파일 전송 프로토콜 완벽 가이드 | FTP·SFTP·FTPS·SMB·NFS·SCP 비교
이 글의 핵심
FTP, SFTP, FTPS, SMB/CIFS, NFS, SCP, rsync 등 파일 전송 및 공유 프로토콜의 동작 원리, 보안, 성능 비교. 실전 구현 예제와 프로덕션 배포까지 완벽 마스터.
들어가며: 파일 전송의 역사와 현대
”파일을 어떻게 안전하게 전송할까?”
서버에 파일을 업로드하거나, 팀원과 대용량 파일을 공유하거나, 백업을 원격 서버로 전송할 때 어떤 프로토콜을 사용하시나요? FTP는 오래되었지만 여전히 널리 사용되고, SFTP는 보안이 강화되었으며, SMB는 Windows 네트워크 공유의 표준입니다. 각 프로토콜마다 보안, 성능, 호환성이 다르며, 잘못 선택하면 데이터 유출이나 성능 저하가 발생합니다. 이 글에서 다루는 프로토콜:
- FTP (File Transfer Protocol)
- SFTP (SSH File Transfer Protocol)
- FTPS (FTP over SSL/TLS)
- SMB/CIFS (Server Message Block)
- NFS (Network File System)
- SCP (Secure Copy Protocol)
- rsync (Remote Sync)
실전 경험에서 배운 교훈
이 기술을 실무 프로젝트에 처음 도입했을 때, 공식 문서만으로는 알 수 없었던 많은 함정들이 있었습니다. 특히 프로덕션 환경에서 발생하는 엣지 케이스들은 로컬 개발 환경에서는 재현조차 되지 않았죠.
가장 어려웠던 점은 성능 최적화였습니다. 처음엔 “동작만 하면 되겠지”라고 생각했지만, 실제 사용자 트래픽이 몰리면서 병목 지점들이 하나씩 드러났습니다. 특히 데이터베이스 쿼리 최적화, 캐싱 전략, 에러 핸들링 구조 등은 여러 번의 장애를 겪으면서 개선해 나갔습니다.
이 글에서는 그런 시행착오를 통해 얻은 실전 노하우와, “이렇게 하면 안 된다”는 교훈들을 함께 정리했습니다. 특히 트러블슈팅 섹션은 실제 장애 대응 경험을 바탕으로 작성했으니, 비슷한 문제를 마주했을 때 참고하시면 도움이 될 것입니다.
1. FTP (File Transfer Protocol)
FTP란?
FTP는 1971년에 개발된 파일 전송 프로토콜로, TCP/IP 기반으로 클라이언트와 서버 간 파일을 주고받습니다.
FTP 동작 원리
sequenceDiagram
participant C as Client
participant CS as Control\nPort 21
participant DS as Data\nPort 20
C->>CS: USER username
CS-->>C: 331 Password required
C->>CS: PASS password
CS-->>C: 230 Login successful
C->>CS: PASV (Passive Mode)
CS-->>C: 227 Entering Passive Mode (IP,Port)
C->>DS: Connect to data port
C->>CS: LIST
CS->>DS: Send file list
DS-->>C: File list data
CS-->>C: 226 Transfer complete
C->>CS: RETR file.txt
CS->>DS: Send file data
DS-->>C: File content
CS-->>C: 226 Transfer complete
C->>CS: QUIT
CS-->>C: 221 Goodbye
FTP 모드
Active Mode (능동 모드)
flowchart LR
subgraph Client["Client\nIP: 192.168.1.100"]
CP["Control Port\nRandom: 50000"]
DP["Data Port\nRandom: 50001"]
end
subgraph Server["Server\nIP: 203.0.113.1"]
CS["Control Port\n21"]
DS["Data Port\n20"]
end
CP -->|1. Control Connection| CS
DS -->|2. Data Connection\nServer → Client| DP
style DS fill:#f96
style DP fill:#6cf
문제점: 클라이언트 방화벽이 서버의 연결을 차단할 수 있음
Passive Mode (수동 모드)
flowchart LR
subgraph Client["Client\nIP: 192.168.1.100"]
CP["Control Port\nRandom: 50000"]
DP["Data Port\nRandom: 50001"]
end
subgraph Server["Server\nIP: 203.0.113.1"]
CS["Control Port\n21"]
DS["Data Port\nRandom: 60000"]
end
CP -->|1. Control Connection| CS
DP -->|2. Data Connection\nClient → Server| DS
style DP fill:#6cf
style DS fill:#f96
장점: 클라이언트가 모든 연결을 시작하므로 방화벽 친화적
FTP 명령어
# FTP 연결
ftp ftp.example.com
# 또는
ftp 203.0.113.1
# 로그인
Name: username
Password: ******
# 기본 명령어
ftp> ls # 디렉토리 목록
ftp> cd /path/to/dir # 디렉토리 이동
ftp> pwd # 현재 디렉토리
ftp> mkdir newdir # 디렉토리 생성
ftp> rmdir olddir # 디렉토리 삭제
# 파일 전송
ftp> get remote.txt # 다운로드
ftp> put local.txt # 업로드
ftp> mget *.txt # 여러 파일 다운로드
ftp> mput *.log # 여러 파일 업로드
# 전송 모드
ftp> binary # 바이너리 모드 (기본)
ftp> ascii # ASCII 모드
# 수동 모드
ftp> passive # Passive 모드 활성화
# 종료
ftp> bye
FTP 서버 구현 (Python)
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
def setup_ftp_server():
"""FTP 서버 설정"""
# 사용자 인증
authorizer = DummyAuthorizer()
# 사용자 추가 (username, password, homedir, perm)
authorizer.add_user("user", "12345", "/home/ftp", perm="elradfmw")
# 익명 사용자
authorizer.add_anonymous("/home/ftp/public", perm="elr")
# 핸들러 설정
handler = FTPHandler
handler.authorizer = authorizer
# 배너 설정
handler.banner = "Welcome to My FTP Server"
# Passive 모드 포트 범위
handler.passive_ports = range(60000, 60100)
# 서버 시작
server = FTPServer(("0.0.0.0", 21), handler)
# 연결 제한
server.max_cons = 256
server.max_cons_per_ip = 5
print("FTP Server running on port 21...")
server.serve_forever()
if __name__ == "__main__":
setup_ftp_server()
FTP 클라이언트 구현
from ftplib import FTP
import os
class FTPClient:
def __init__(self, host, username, password):
self.ftp = FTP()
self.ftp.connect(host, 21)
self.ftp.login(username, password)
print(f"✅ Connected to {host}")
def list_files(self, path='/'):
"""파일 목록 조회"""
self.ftp.cwd(path)
files = []
self.ftp.retrlines('LIST', files.append)
return files
def download_file(self, remote_path, local_path):
"""파일 다운로드"""
with open(local_path, 'wb') as f:
self.ftp.retrbinary(f'RETR {remote_path}', f.write)
print(f"✅ Downloaded: {remote_path} → {local_path}")
def upload_file(self, local_path, remote_path):
"""파일 업로드"""
with open(local_path, 'rb') as f:
self.ftp.storbinary(f'STOR {remote_path}', f)
print(f"✅ Uploaded: {local_path} → {remote_path}")
def download_directory(self, remote_dir, local_dir):
"""디렉토리 재귀 다운로드"""
os.makedirs(local_dir, exist_ok=True)
self.ftp.cwd(remote_dir)
for item in self.ftp.nlst():
local_path = os.path.join(local_dir, item)
remote_path = f"{remote_dir}/{item}"
try:
# 디렉토리인지 확인
self.ftp.cwd(remote_path)
self.ftp.cwd('..')
# 디렉토리면 재귀 호출
self.download_directory(remote_path, local_path)
except:
# 파일이면 다운로드
self.download_file(item, local_path)
def close(self):
self.ftp.quit()
print("✅ Connection closed")
# 사용 예시
client = FTPClient('ftp.example.com', 'user', 'pass')
client.list_files('/')
client.download_file('report.pdf', 'local_report.pdf')
client.upload_file('data.csv', 'remote_data.csv')
client.close()
FTP 보안 문제
flowchart TB
FTP[FTP 프로토콜]
subgraph Issues[보안 취약점]
I1["🔓 평문 전송\nID/PW 노출"]
I2["🔓 데이터 암호화 없음\n파일 내용 노출"]
I3["🔓 중간자 공격\nMan-in-the-Middle"]
I4["🔓 스니핑 가능\nWireshark로 캡처"]
end
subgraph Solutions[해결책]
S1["✅ SFTP 사용\nSSH 암호화"]
S2["✅ FTPS 사용\nTLS 암호화"]
S3["✅ VPN 터널링\n네트워크 암호화"]
end
FTP --> Issues
Issues --> Solutions
2. SFTP (SSH File Transfer Protocol)
SFTP란?
SFTP는 SSH 프로토콜 위에서 동작하는 파일 전송 프로토콜로, 모든 데이터가 암호화됩니다. FTP와 이름이 비슷하지만 완전히 다른 프로토콜입니다.
SFTP vs FTP
| 특성 | FTP | SFTP |
|---|---|---|
| 프로토콜 | 독립 프로토콜 | SSH 기반 |
| 포트 | 21 (제어), 20 (데이터) | 22 (단일) |
| 암호화 | ❌ 평문 | ✅ SSH 암호화 |
| 인증 | ID/PW | ID/PW, 공개키 |
| 방화벽 | 복잡 (2개 포트) | 간단 (1개 포트) |
| 속도 | 빠름 | 약간 느림 (암호화 오버헤드) |
SFTP 동작 원리
sequenceDiagram
participant C as Client
participant S as Server\nPort 22
C->>S: SSH 연결 요청
S-->>C: 서버 공개키
C->>S: 클라이언트 인증 (ID/PW 또는 키)
S-->>C: 인증 성공
Note over C,S: SSH 터널 내부에서 SFTP 세션
C->>S: SFTP 세션 시작
S-->>C: SFTP 프로토콜 버전
C->>S: SSH_FXP_OPENDIR /path
S-->>C: SSH_FXP_HANDLE (핸들)
C->>S: SSH_FXP_READDIR (핸들)
S-->>C: SSH_FXP_NAME (파일 목록)
C->>S: SSH_FXP_OPEN file.txt
S-->>C: SSH_FXP_HANDLE
C->>S: SSH_FXP_READ (핸들, offset, length)
S-->>C: SSH_FXP_DATA (파일 데이터)
C->>S: SSH_FXP_CLOSE (핸들)
S-->>C: SSH_FXP_STATUS OK
SFTP 명령어
# SFTP 연결
sftp [email protected]
# 또는 포트 지정
sftp -P 2222 [email protected]
# 공개키 인증
sftp -i ~/.ssh/id_rsa [email protected]
# 기본 명령어
sftp> ls # 원격 디렉토리 목록
sftp> lls # 로컬 디렉토리 목록
sftp> cd /remote/path # 원격 디렉토리 이동
sftp> lcd /local/path # 로컬 디렉토리 이동
sftp> pwd # 원격 현재 디렉토리
sftp> lpwd # 로컬 현재 디렉토리
# 파일 전송
sftp> get remote.txt # 다운로드
sftp> put local.txt # 업로드
sftp> get -r remotedir # 디렉토리 다운로드
sftp> put -r localdir # 디렉토리 업로드
# 파일 관리
sftp> mkdir newdir # 디렉토리 생성
sftp> rmdir olddir # 디렉토리 삭제
sftp> rm file.txt # 파일 삭제
sftp> rename old.txt new.txt # 파일 이름 변경
# 권한 관리
sftp> chmod 644 file.txt # 권한 변경
sftp> chown user file.txt # 소유자 변경
# 종료
sftp> exit
SFTP 클라이언트 구현 (Python)
import paramiko
import os
from pathlib import Path
class SFTPClient:
def __init__(self, host, port, username, password=None, key_file=None):
"""SFTP 클라이언트 초기화"""
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if key_file:
# 공개키 인증
private_key = paramiko.RSAKey.from_private_key_file(key_file)
self.ssh.connect(host, port, username, pkey=private_key)
else:
# 비밀번호 인증
self.ssh.connect(host, port, username, password)
self.sftp = self.ssh.open_sftp()
print(f"✅ Connected to {host}:{port}")
def list_files(self, remote_path='/'):
"""파일 목록 조회"""
return self.sftp.listdir(remote_path)
def download_file(self, remote_path, local_path):
"""파일 다운로드"""
self.sftp.get(remote_path, local_path)
print(f"✅ Downloaded: {remote_path} → {local_path}")
def upload_file(self, local_path, remote_path):
"""파일 업로드"""
self.sftp.put(local_path, remote_path)
print(f"✅ Uploaded: {local_path} → {remote_path}")
def download_directory(self, remote_dir, local_dir):
"""디렉토리 재귀 다운로드"""
os.makedirs(local_dir, exist_ok=True)
for item in self.sftp.listdir_attr(remote_dir):
remote_path = f"{remote_dir}/{item.filename}"
local_path = os.path.join(local_dir, item.filename)
if self._is_directory(item):
self.download_directory(remote_path, local_path)
else:
self.download_file(remote_path, local_path)
def upload_directory(self, local_dir, remote_dir):
"""디렉토리 재귀 업로드"""
try:
self.sftp.mkdir(remote_dir)
except IOError:
pass
for item in os.listdir(local_dir):
local_path = os.path.join(local_dir, item)
remote_path = f"{remote_dir}/{item}"
if os.path.isdir(local_path):
self.upload_directory(local_path, remote_path)
else:
self.upload_file(local_path, remote_path)
def _is_directory(self, item):
"""디렉토리 여부 확인"""
import stat
return stat.S_ISDIR(item.st_mode)
def close(self):
"""연결 종료"""
self.sftp.close()
self.ssh.close()
print("✅ Connection closed")
# 사용 예시
client = SFTPClient('example.com', 22, 'user', password='pass')
# 또는 공개키 인증
# client = SFTPClient('example.com', 22, 'user', key_file='~/.ssh/id_rsa')
files = client.list_files('/uploads')
print(files)
client.download_file('/uploads/report.pdf', 'local_report.pdf')
client.upload_file('data.csv', '/uploads/data.csv')
client.download_directory('/uploads/project', './project')
client.close()
SFTP 서버 설정 (OpenSSH)
# /etc/ssh/sshd_config
# SFTP 서브시스템 활성화
Subsystem sftp /usr/lib/openssh/sftp-server
# SFTP 전용 사용자 생성
Match User sftpuser
ChrootDirectory /home/sftpuser
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
# 설정 적용
sudo systemctl restart sshd
3. FTPS (FTP over SSL/TLS)
FTPS란?
FTPS는 FTP에 SSL/TLS 암호화를 추가한 프로토콜로, HTTPS와 유사하게 보안 계층을 제공합니다.
FTPS 모드
Explicit FTPS (FTPES)
sequenceDiagram
participant C as Client
participant S as Server\nPort 21
C->>S: Connect (평문)
S-->>C: 220 Welcome
C->>S: AUTH TLS
S-->>C: 234 Proceed with negotiation
Note over C,S: TLS 핸드셰이크
C->>S: TLS ClientHello
S-->>C: TLS ServerHello, Certificate
C->>S: TLS Finished
Note over C,S: 암호화된 연결
C->>S: USER username (암호화)
S-->>C: 331 Password required
C->>S: PASS password (암호화)
S-->>C: 230 Login successful
포트: 21 (제어), 평문으로 시작 후 TLS로 업그레이드
Implicit FTPS
sequenceDiagram
participant C as Client
participant S as Server\nPort 990
Note over C,S: 처음부터 TLS 암호화
C->>S: TLS ClientHello
S-->>C: TLS ServerHello, Certificate
C->>S: TLS Finished
Note over C,S: 암호화된 FTP 세션
C->>S: USER username (암호화)
S-->>C: 331 Password required
C->>S: PASS password (암호화)
S-->>C: 230 Login successful
포트: 990 (제어), 처음부터 TLS 암호화
FTPS 클라이언트 (Python)
from ftplib import FTP_TLS
import ssl
class FTPSClient:
def __init__(self, host, username, password, implicit=False):
"""FTPS 클라이언트 초기화"""
if implicit:
# Implicit FTPS (포트 990)
context = ssl.create_default_context()
self.ftp = FTP_TLS(context=context)
self.ftp.connect(host, 990)
else:
# Explicit FTPS (포트 21)
self.ftp = FTP_TLS()
self.ftp.connect(host, 21)
self.ftp.login(username, password)
# 데이터 연결도 암호화
self.ftp.prot_p()
print(f"✅ Secure connection to {host}")
def download_file(self, remote_path, local_path):
"""파일 다운로드"""
with open(local_path, 'wb') as f:
self.ftp.retrbinary(f'RETR {remote_path}', f.write)
print(f"✅ Downloaded: {remote_path}")
def upload_file(self, local_path, remote_path):
"""파일 업로드"""
with open(local_path, 'rb') as f:
self.ftp.storbinary(f'STOR {remote_path}', f)
print(f"✅ Uploaded: {local_path}")
def close(self):
self.ftp.quit()
# 사용
client = FTPSClient('ftps.example.com', 'user', 'pass')
client.download_file('secure.pdf', 'local.pdf')
client.close()
4. SMB/CIFS (Server Message Block)
SMB란?
SMB는 Windows 네트워크 파일 공유 프로토콜로, Samba는 Linux에서 SMB를 구현한 오픈소스입니다.
SMB 버전 비교
| 버전 | 출시 | 주요 특징 |
|---|---|---|
| SMB 1.0 | 1984 | 레거시, 보안 취약점 다수 |
| SMB 2.0 | 2006 | 성능 개선, 파이프라이닝 |
| SMB 2.1 | 2010 | 대용량 MTU 지원 |
| SMB 3.0 | 2012 | 암호화, 멀티채널, RDMA |
| SMB 3.1.1 | 2015 | AES-128-GCM, 사전 인증 무결성 |
SMB 동작 원리
sequenceDiagram
participant C as Client
participant S as Server\nPort 445
C->>S: SMB Negotiate Protocol
S-->>C: SMB2/SMB3 지원 확인
C->>S: Session Setup (인증)
S-->>C: Session ID
C->>S: Tree Connect (\\server\share)
S-->>C: Tree ID
C->>S: Create (파일 열기)
S-->>C: File ID
C->>S: Read (File ID, offset, length)
S-->>C: File Data
C->>S: Write (File ID, offset, data)
S-->>C: Write Complete
C->>S: Close (File ID)
S-->>C: Close Complete
C->>S: Tree Disconnect
S-->>C: Disconnect OK
Samba 서버 설정
# Samba 설치 (Ubuntu/Debian)
sudo apt update
sudo apt install samba samba-common-bin
# Samba 사용자 추가
sudo smbpasswd -a username
# /etc/samba/smb.conf 설정
sudo nano /etc/samba/smb.conf
# /etc/samba/smb.conf
[global]
workgroup = WORKGROUP
server string = Samba Server
security = user
map to guest = Bad User
# SMB 버전 제한 (보안)
min protocol = SMB2
max protocol = SMB3
# 로깅
log file = /var/log/samba/%m.log
max log size = 50
# 성능 최적화
socket options = TCP_NODELAY IPTOS_LOWDELAY
read raw = yes
write raw = yes
# 암호화 (SMB 3.0+)
smb encrypt = desired
[public]
comment = Public Share
path = /srv/samba/public
browseable = yes
read only = yes
guest ok = yes
[private]
comment = Private Share
path = /srv/samba/private
browseable = yes
read only = no
valid users = @staff
create mask = 0660
directory mask = 0770
force group = staff
[homes]
comment = Home Directories
browseable = no
read only = no
create mask = 0700
directory mask = 0700
valid users = %S
# 디렉토리 생성 및 권한 설정
sudo mkdir -p /srv/samba/{public,private}
sudo chmod 755 /srv/samba/public
sudo chmod 770 /srv/samba/private
sudo chown -R root:staff /srv/samba/private
# Samba 서비스 재시작
sudo systemctl restart smbd
sudo systemctl enable smbd
# 방화벽 설정
sudo ufw allow 445/tcp
sudo ufw allow 139/tcp
SMB 클라이언트 (Python)
from smb.SMBConnection import SMBConnection
import tempfile
class SMBClient:
def __init__(self, host, username, password, domain='WORKGROUP', share_name='public'):
"""SMB 클라이언트 초기화"""
self.conn = SMBConnection(
username,
password,
'client_machine',
host,
domain=domain,
use_ntlm_v2=True,
is_direct_tcp=True
)
if not self.conn.connect(host, 445):
raise Exception("Connection failed")
self.share_name = share_name
print(f"✅ Connected to \\\\{host}\\{share_name}")
def list_files(self, path='/'):
"""파일 목록 조회"""
files = self.conn.listPath(self.share_name, path)
return [f.filename for f in files if f.filename not in ['.', '..']]
def download_file(self, remote_path, local_path):
"""파일 다운로드"""
with open(local_path, 'wb') as f:
self.conn.retrieveFile(self.share_name, remote_path, f)
print(f"✅ Downloaded: {remote_path}")
def upload_file(self, local_path, remote_path):
"""파일 업로드"""
with open(local_path, 'rb') as f:
self.conn.storeFile(self.share_name, remote_path, f)
print(f"✅ Uploaded: {local_path}")
def create_directory(self, path):
"""디렉토리 생성"""
self.conn.createDirectory(self.share_name, path)
print(f"✅ Created directory: {path}")
def delete_file(self, path):
"""파일 삭제"""
self.conn.deleteFiles(self.share_name, path)
print(f"✅ Deleted: {path}")
def close(self):
"""연결 종료"""
self.conn.close()
print("✅ Connection closed")
# 사용 예시
client = SMBClient('192.168.1.100', 'user', 'pass', share_name='shared')
files = client.list_files('/')
print(files)
client.download_file('/documents/report.pdf', 'report.pdf')
client.upload_file('data.csv', '/uploads/data.csv')
client.close()
Windows에서 SMB 공유 마운트
# Windows (CMD)
net use Z: \\192.168.1.100\shared /user:username password
# Windows (PowerShell)
New-PSDrive -Name "Z" -PSProvider FileSystem -Root "\\192.168.1.100\shared" -Credential (Get-Credential)
# Linux (CIFS 마운트)
sudo apt install cifs-utils
sudo mount -t cifs //192.168.1.100/shared /mnt/shared -o username=user,password=pass
# 영구 마운트 (/etc/fstab)
//192.168.1.100/shared /mnt/shared cifs credentials=/etc/samba/credentials,uid=1000,gid=1000 0 0
# /etc/samba/credentials
username=user
password=pass
5. NFS (Network File System)
NFS란?
NFS는 Unix/Linux 환경에서 파일 시스템을 네트워크로 공유하는 프로토콜입니다. 로컬 파일 시스템처럼 투명하게 사용할 수 있습니다.
NFS 버전
| 버전 | 특징 |
|---|---|
| NFSv3 | UDP/TCP, Stateless, 널리 사용 |
| NFSv4 | TCP만, Stateful, 방화벽 친화적 (단일 포트 2049), ACL 지원 |
| NFSv4.1 | pNFS (병렬 NFS), 세션 관리 |
| NFSv4.2 | 서버 사이드 복사, 희소 파일 |
NFS 동작 원리
flowchart TB
subgraph Client[NFS Client]
App[Application]
VFS[VFS Layer]
NFSC[NFS Client]
end
subgraph Network[Network]
RPC[RPC/XDR]
end
subgraph Server[NFS Server]
NFSD[NFS Daemon]
FS[File System]
Disk[Disk]
end
App -->|read/write| VFS
VFS -->|NFS call| NFSC
NFSC -->|RPC| RPC
RPC -->|Network| NFSD
NFSD -->|I/O| FS
FS -->|Storage| Disk
NFS 서버 설정
# NFS 서버 설치 (Ubuntu/Debian)
sudo apt install nfs-kernel-server
# 공유 디렉토리 생성
sudo mkdir -p /srv/nfs/shared
sudo chown nobody:nogroup /srv/nfs/shared
sudo chmod 755 /srv/nfs/shared
# /etc/exports 설정
sudo nano /etc/exports
# /etc/exports
# 형식: 공유경로 클라이언트(옵션)
# 특정 IP만 허용
/srv/nfs/shared 192.168.1.100(rw,sync,no_subtree_check)
# 서브넷 허용
/srv/nfs/shared 192.168.1.0/24(rw,sync,no_subtree_check)
# 읽기 전용
/srv/nfs/public 192.168.1.0/24(ro,sync,no_subtree_check)
# 여러 클라이언트
/srv/nfs/shared 192.168.1.100(rw,sync) 192.168.1.101(rw,sync)
# 옵션 설명:
# rw: 읽기/쓰기 허용
# ro: 읽기만 허용
# sync: 동기 쓰기 (데이터 무결성)
# async: 비동기 쓰기 (성능 향상, 위험)
# no_subtree_check: 서브트리 검사 비활성화 (성능)
# root_squash: root를 nobody로 매핑 (보안)
# no_root_squash: root 권한 유지 (위험)
# exports 적용
sudo exportfs -ra
# 현재 공유 목록 확인
sudo exportfs -v
# NFS 서버 시작
sudo systemctl start nfs-kernel-server
sudo systemctl enable nfs-kernel-server
# 방화벽 설정
sudo ufw allow from 192.168.1.0/24 to any port nfs
NFS 클라이언트 마운트
# NFS 클라이언트 설치
sudo apt install nfs-common
# 마운트
sudo mkdir -p /mnt/nfs/shared
sudo mount -t nfs 192.168.1.100:/srv/nfs/shared /mnt/nfs/shared
# 옵션 지정
sudo mount -t nfs -o rw,hard,intr,timeo=600 192.168.1.100:/srv/nfs/shared /mnt/nfs/shared
# 영구 마운트 (/etc/fstab)
192.168.1.100:/srv/nfs/shared /mnt/nfs/shared nfs defaults,_netdev 0 0
# 마운트 확인
df -h | grep nfs
mount | grep nfs
# 언마운트
sudo umount /mnt/nfs/shared
NFS 성능 최적화
# /etc/fstab 최적화 옵션
192.168.1.100:/srv/nfs/shared /mnt/nfs/shared nfs rw,hard,intr,rsize=32768,wsize=32768,timeo=600,retrans=2,_netdev 0 0
# 옵션 설명:
# rsize=32768: 읽기 버퍼 크기 (32KB)
# wsize=32768: 쓰기 버퍼 크기 (32KB)
# hard: 서버 응답 없으면 무한 재시도 (데이터 무결성)
# soft: 타임아웃 후 에러 반환 (성능)
# intr: 인터럽트 가능
# timeo=600: 타임아웃 (0.1초 단위, 60초)
# retrans=2: 재전송 횟수
# _netdev: 네트워크 활성화 후 마운트
6. SCP와 rsync
SCP (Secure Copy Protocol)
SCP는 SSH를 사용하여 파일을 안전하게 복사하는 프로토콜입니다.
# 기본 사용법
scp local.txt [email protected]:/remote/path/
# 원격 → 로컬
scp [email protected]:/remote/file.txt ./local.txt
# 디렉토리 복사 (재귀)
scp -r localdir/ [email protected]:/remote/path/
# 포트 지정
scp -P 2222 file.txt [email protected]:/path/
# 공개키 인증
scp -i ~/.ssh/id_rsa file.txt [email protected]:/path/
# 대역폭 제한 (KB/s)
scp -l 1024 large.zip [email protected]:/path/
# 압축 전송
scp -C large.tar [email protected]:/path/
# 진행률 표시
scp -v file.txt [email protected]:/path/
# 여러 파일
scp file1.txt file2.txt [email protected]:/path/
# 와일드카드
scp *.log [email protected]:/logs/
rsync (Remote Sync)
rsync는 효율적인 파일 동기화 도구로, 변경된 부분만 전송합니다.
# 기본 사용법
rsync -avz localdir/ [email protected]:/remote/path/
# 옵션 설명:
# -a: archive (권한, 타임스탬프, 심볼릭 링크 유지)
# -v: verbose (진행 상황 표시)
# -z: compress (압축 전송)
# -h: human-readable (사람이 읽기 쉬운 크기)
# -P: progress + partial (진행률 + 중단된 전송 재개)
# --delete: 원본에 없는 파일 삭제 (동기화)
# 진행률 표시
rsync -avzP localdir/ [email protected]:/remote/path/
# 삭제 동기화
rsync -avz --delete localdir/ [email protected]:/remote/path/
# Dry-run (실제 전송 없이 테스트)
rsync -avzn localdir/ [email protected]:/remote/path/
# 특정 파일 제외
rsync -avz --exclude='*.log' --exclude='node_modules/' localdir/ [email protected]:/remote/
# SSH 포트 지정
rsync -avz -e "ssh -p 2222" localdir/ [email protected]:/remote/
# 대역폭 제한 (KB/s)
rsync -avz --bwlimit=1024 localdir/ [email protected]:/remote/
# 로컬 동기화
rsync -avz /source/ /destination/
# 백업 (타임스탬프 디렉토리)
rsync -avz --backup --backup-dir=/backup/$(date +%Y%m%d) /source/ /destination/
rsync 고급 사용
# 증분 백업 (하드링크 사용)
#!/bin/bash
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
LATEST="$BACKUP_DIR/latest"
CURRENT="$BACKUP_DIR/$DATE"
rsync -avz --delete \
--link-dest="$LATEST" \
/source/ \
"$CURRENT/"
# 심볼릭 링크 업데이트
rm -f "$LATEST"
ln -s "$CURRENT" "$LATEST"
echo "✅ Backup completed: $CURRENT"
# 원격 서버 백업
#!/bin/bash
rsync -avz --delete \
-e "ssh -i ~/.ssh/backup_key" \
--exclude='*.tmp' \
--exclude='cache/' \
/var/www/ \
[email protected]:/backups/www/
# 양방향 동기화 (주의: 충돌 가능)
rsync -avz --delete /local/ user@remote:/remote/
rsync -avz --delete user@remote:/remote/ /local/
7. 프로토콜 비교
종합 비교표
| 프로토콜 | 포트 | 암호화 | 속도 | 사용 사례 | 장점 | 단점 |
|---|---|---|---|---|---|---|
| FTP | 21, 20 | ❌ | ⭐⭐⭐⭐⭐ | 레거시 시스템 | 빠름, 간단 | 보안 취약 |
| SFTP | 22 | ✅ SSH | ⭐⭐⭐⭐ | 안전한 파일 전송 | 보안, 방화벽 친화적 | 약간 느림 |
| FTPS | 21, 990 | ✅ TLS | ⭐⭐⭐⭐ | 레거시 FTP 보안 | FTP 호환 | 복잡한 방화벽 |
| SMB | 445 | ✅ (v3+) | ⭐⭐⭐⭐ | Windows 공유 | 권한 관리 강력 | Windows 중심 |
| NFS | 2049 | ✅ (v4+) | ⭐⭐⭐⭐⭐ | Linux 서버 공유 | 빠름, 투명 | Unix 중심 |
| SCP | 22 | ✅ SSH | ⭐⭐⭐ | 간단한 파일 복사 | 간단, 안전 | 동기화 불가 |
| rsync | 22, 873 | ✅ SSH | ⭐⭐⭐⭐⭐ | 파일 동기화, 백업 | 증분 전송, 효율적 | 초기 학습 곡선 |
보안 비교
flowchart TB
subgraph Insecure[🔓 암호화 없음]
FTP["FTP\n평문 전송"]
NFSv3["NFSv3\n기본 암호화 없음"]
end
subgraph Secure[🔒 암호화 지원]
SFTP["SFTP\nSSH 암호화"]
FTPS["FTPS\nTLS 암호화"]
SMB3["SMB 3.0+\nAES 암호화"]
NFSv4["NFSv4\nKerberos"]
SCP["SCP\nSSH 암호화"]
RSYNC["rsync over SSH\nSSH 암호화"]
end
style Insecure fill:#fcc
style Secure fill:#cfc
성능 비교 (벤치마크)
#!/bin/bash
# 1GB 파일 전송 벤치마크
FILE="test_1gb.bin"
dd if=/dev/urandom of=$FILE bs=1M count=1024
echo "=== FTP ==="
time ftp -n <<EOF
open ftp.example.com
user username password
binary
put $FILE
bye
EOF
echo "=== SFTP ==="
time sftp [email protected] <<EOF
put $FILE
bye
EOF
echo "=== SCP ==="
time scp $FILE [email protected]:/tmp/
echo "=== rsync ==="
time rsync -avz $FILE [email protected]:/tmp/
echo "=== SMB ==="
time cp $FILE /mnt/smb/
echo "=== NFS ==="
time cp $FILE /mnt/nfs/
# 일반적인 결과 (1Gbps 네트워크):
# FTP: ~10초 (가장 빠름)
# NFS: ~12초
# SMB: ~15초
# rsync: ~18초 (압축 오버헤드)
# SFTP: ~20초 (암호화 오버헤드)
# SCP: ~22초
8. 실전 구현
시나리오 1: 자동 백업 스크립트
#!/bin/bash
# backup.sh - 일일 백업 스크립트
BACKUP_DATE=$(date +%Y%m%d)
SOURCE="/var/www"
REMOTE_HOST="backup.example.com"
REMOTE_USER="backup"
REMOTE_PATH="/backups/www"
LOG_FILE="/var/log/backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Starting backup..."
# rsync로 증분 백업
rsync -avz --delete \
--exclude='*.tmp' \
--exclude='cache/' \
--exclude='node_modules/' \
--backup --backup-dir="$REMOTE_PATH/deleted_$BACKUP_DATE" \
-e "ssh -i /root/.ssh/backup_key -o StrictHostKeyChecking=no" \
"$SOURCE/" \
"$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/current/" \
2>&1 | tee -a "$LOG_FILE"
if [ ${PIPESTATUS[0]} -eq 0 ]; then
log "✅ Backup completed successfully"
# 원격 서버에서 7일 이상 된 백업 삭제
ssh -i /root/.ssh/backup_key "$REMOTE_USER@$REMOTE_HOST" \
"find $REMOTE_PATH/deleted_* -mtime +7 -delete"
log "✅ Old backups cleaned"
else
log "❌ Backup failed"
exit 1
fi
# crontab 등록
crontab -e
# 매일 새벽 2시 백업
0 2 * * * /usr/local/bin/backup.sh
시나리오 2: 파일 업로드 서버
#!/usr/bin/env python3
"""
멀티 프로토콜 파일 업로드 서버
"""
import paramiko
from ftplib import FTP_TLS
import os
from pathlib import Path
class FileUploadServer:
def __init__(self, protocol='sftp', host='localhost', username='user', password='pass'):
self.protocol = protocol
self.host = host
self.username = username
self.password = password
self.client = None
if protocol == 'sftp':
self._connect_sftp()
elif protocol == 'ftps':
self._connect_ftps()
else:
raise ValueError(f"Unsupported protocol: {protocol}")
def _connect_sftp(self):
"""SFTP 연결"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.host, 22, self.username, self.password)
self.client = ssh.open_sftp()
print(f"✅ SFTP connected to {self.host}")
def _connect_ftps(self):
"""FTPS 연결"""
self.client = FTP_TLS()
self.client.connect(self.host, 21)
self.client.login(self.username, self.password)
self.client.prot_p()
print(f"✅ FTPS connected to {self.host}")
def upload_with_progress(self, local_path, remote_path):
"""진행률 표시 업로드"""
file_size = os.path.getsize(local_path)
uploaded = 0
def callback(data):
nonlocal uploaded
uploaded += len(data)
progress = (uploaded / file_size) * 100
print(f"\rUploading: {progress:.1f}%", end=')
with open(local_path, 'rb') as f:
if self.protocol == 'sftp':
self.client.putfo(f, remote_path, callback=lambda x, y: callback(b'))
elif self.protocol == 'ftps':
self.client.storbinary(f'STOR {remote_path}', f, callback=callback)
print(f"\n✅ Uploaded: {local_path} → {remote_path}")
def upload_directory(self, local_dir, remote_dir):
"""디렉토리 재귀 업로드"""
for root, dirs, files in os.walk(local_dir):
rel_path = os.path.relpath(root, local_dir)
remote_root = os.path.join(remote_dir, rel_path).replace('\\', '/')
# 디렉토리 생성
try:
if self.protocol == 'sftp':
self.client.mkdir(remote_root)
elif self.protocol == 'ftps':
self.client.mkd(remote_root)
except:
pass
# 파일 업로드
for file in files:
local_path = os.path.join(root, file)
remote_path = os.path.join(remote_root, file).replace('\\', '/')
self.upload_with_progress(local_path, remote_path)
def close(self):
"""연결 종료"""
if self.client:
self.client.close()
print("✅ Connection closed")
# 사용 예시
uploader = FileUploadServer('sftp', 'example.com', 'user', 'pass')
uploader.upload_with_progress('large_file.zip', '/uploads/large_file.zip')
uploader.upload_directory('./project', '/uploads/project')
uploader.close()
시나리오 3: 파일 동기화 데몬
#!/usr/bin/env python3
"""
파일 변경 감지 및 자동 동기화
"""
import time
import subprocess
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class SyncHandler(FileSystemEventHandler):
def __init__(self, local_dir, remote_host, remote_user, remote_dir):
self.local_dir = local_dir
self.remote_host = remote_host
self.remote_user = remote_user
self.remote_dir = remote_dir
self.last_sync = 0
self.sync_interval = 5
def on_any_event(self, event):
"""파일 변경 감지"""
current_time = time.time()
if current_time - self.last_sync < self.sync_interval:
return
if event.is_directory:
return
print(f"📁 File changed: {event.src_path}")
self.sync()
self.last_sync = current_time
def sync(self):
"""rsync로 동기화"""
cmd = [
'rsync',
'-avz',
'--delete',
'--exclude=.git/',
'--exclude=node_modules/',
f'{self.local_dir}/',
f'{self.remote_user}@{self.remote_host}:{self.remote_dir}/'
]
try:
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
print("✅ Sync completed")
else:
print(f"❌ Sync failed: {result.stderr}")
except Exception as e:
print(f"❌ Error: {e}")
def main():
"""파일 감시 시작"""
local_dir = '/var/www/html'
remote_host = 'backup.example.com'
remote_user = 'backup'
remote_dir = '/backups/www'
event_handler = SyncHandler(local_dir, remote_host, remote_user, remote_dir)
observer = Observer()
observer.schedule(event_handler, local_dir, recursive=True)
observer.start()
print(f"👀 Watching {local_dir} for changes...")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
print("\n✅ Stopped watching")
observer.join()
if __name__ == "__main__":
main()
실전 시나리오
시나리오 4: Docker 컨테이너에서 SFTP 서버
# Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
openssh-server \
&& rm -rf /var/lib/apt/lists/*
# SFTP 전용 사용자 생성
RUN useradd -m -d /home/sftpuser -s /bin/bash sftpuser && \
echo "sftpuser:password" | chpasswd && \
mkdir -p /home/sftpuser/uploads && \
chown root:root /home/sftpuser && \
chmod 755 /home/sftpuser && \
chown sftpuser:sftpuser /home/sftpuser/uploads
# SSH 설정
RUN mkdir /var/run/sshd && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config
# SFTP 전용 설정 추가
RUN echo "\n\
Match User sftpuser\n\
ChrootDirectory /home/sftpuser\n\
ForceCommand internal-sftp\n\
AllowTcpForwarding no\n\
X11Forwarding no\n\
" >> /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
# docker-compose.yml
version: '3.8'
services:
sftp:
build: .
ports:
- "2222:22"
volumes:
- ./uploads:/home/sftpuser/uploads
environment:
- SFTP_USERS=user:pass:1001
restart: unless-stopped
# 빌드 및 실행
docker-compose up -d
# 연결 테스트
sftp -P 2222 sftpuser@localhost
시나리오 5: 멀티 프로토콜 파일 서버
#!/usr/bin/env python3
"""
통합 파일 서버 (SFTP + FTPS + HTTP)
"""
import asyncio
from aiohttp import web
import paramiko
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import TLS_FTPHandler
from pyftpdlib.servers import FTPServer
import threading
class MultiProtocolFileServer:
def __init__(self, base_dir='/srv/files'):
self.base_dir = base_dir
def start_sftp_server(self):
"""SFTP 서버 시작"""
class SFTPServer(paramiko.ServerInterface):
def check_auth_password(self, username, password):
if username == 'user' and password == 'pass':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
# SSH 서버 설정
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 2222))
sock.listen(10)
print("✅ SFTP Server running on port 2222")
while True:
client, addr = sock.accept()
print(f"📥 SFTP connection from {addr}")
# 클라이언트 처리 (생략)
def start_ftps_server(self):
"""FTPS 서버 시작"""
authorizer = DummyAuthorizer()
authorizer.add_user("user", "pass", self.base_dir, perm="elradfmw")
handler = TLS_FTPHandler
handler.authorizer = authorizer
handler.certfile = '/etc/ssl/certs/server.crt'
handler.keyfile = '/etc/ssl/private/server.key'
handler.tls_control_required = True
handler.tls_data_required = True
server = FTPServer(('0.0.0.0', 990), handler)
print("✅ FTPS Server running on port 990")
server.serve_forever()
async def start_http_server(self):
"""HTTP 파일 다운로드 서버"""
async def download(request):
filename = request.match_info['filename']
filepath = os.path.join(self.base_dir, filename)
if not os.path.exists(filepath):
return web.Response(status=404, text="File not found")
return web.FileResponse(filepath)
app = web.Application()
app.router.add_get('/download/{filename}', download)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, '0.0.0.0', 8080)
await site.start()
print("✅ HTTP Server running on port 8080")
await asyncio.Event().wait()
def start(self):
"""모든 서버 시작"""
# FTPS 서버 (별도 스레드)
ftps_thread = threading.Thread(target=self.start_ftps_server, daemon=True)
ftps_thread.start()
# HTTP 서버 (asyncio)
asyncio.run(self.start_http_server())
if __name__ == "__main__":
server = MultiProtocolFileServer('/srv/files')
server.start()
시나리오 6: 파일 전송 모니터링
#!/usr/bin/env python3
"""
파일 전송 모니터링 및 알림
"""
import paramiko
import time
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
class TransferMonitor:
def __init__(self, sftp_host, sftp_user, sftp_pass, watch_dir='/uploads'):
self.sftp_host = sftp_host
self.sftp_user = sftp_user
self.sftp_pass = sftp_pass
self.watch_dir = watch_dir
self.known_files = set()
def connect(self):
"""SFTP 연결"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.sftp_host, 22, self.sftp_user, self.sftp_pass)
self.sftp = ssh.open_sftp()
def check_new_files(self):
"""새 파일 확인"""
try:
files = set(self.sftp.listdir(self.watch_dir))
new_files = files - self.known_files
if new_files:
for file in new_files:
file_path = f"{self.watch_dir}/{file}"
stat = self.sftp.stat(file_path)
size = stat.st_size
mtime = datetime.fromtimestamp(stat.st_mtime)
print(f"📥 New file: {file} ({size} bytes) at {mtime}")
self.send_notification(file, size, mtime)
self.known_files = files
except Exception as e:
print(f"❌ Error: {e}")
def send_notification(self, filename, size, mtime):
"""이메일 알림"""
msg = MIMEText(f"""
새 파일이 업로드되었습니다.
파일명: {filename}
크기: {size:,} bytes
시간: {mtime}
경로: {self.watch_dir}/{filename}
""")
msg['Subject'] = f'[파일 업로드] {filename}'
msg['From'] = '[email protected]'
msg['To'] = '[email protected]'
try:
with smtplib.SMTP('localhost', 25) as smtp:
smtp.send_message(msg)
print(f"✅ Notification sent for {filename}")
except Exception as e:
print(f"❌ Failed to send notification: {e}")
def monitor(self, interval=60):
"""주기적 모니터링"""
self.connect()
print(f"👀 Monitoring {self.watch_dir} every {interval}s...")
try:
while True:
self.check_new_files()
time.sleep(interval)
except KeyboardInterrupt:
print("\n✅ Monitoring stopped")
finally:
self.sftp.close()
# 사용
monitor = TransferMonitor('sftp.example.com', 'user', 'pass')
monitor.monitor(interval=60)
보안 베스트 프랙티스
1. 공개키 인증 설정
# 클라이언트에서 키 생성
ssh-keygen -t ed25519 -C "[email protected]"
# 또는 RSA
ssh-keygen -t rsa -b 4096 -C "[email protected]"
# 공개키를 서버에 복사
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
# 또는 수동 복사
cat ~/.ssh/id_ed25519.pub | ssh [email protected] "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
# 서버에서 비밀번호 인증 비활성화
# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
sudo systemctl restart sshd
2. Chroot Jail 설정
# SFTP 사용자를 특정 디렉토리에 격리
# /etc/ssh/sshd_config
Match User sftpuser
ChrootDirectory /home/sftpuser
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
# 디렉토리 권한 (중요!)
sudo chown root:root /home/sftpuser
sudo chmod 755 /home/sftpuser
# 쓰기 가능한 하위 디렉토리
sudo mkdir /home/sftpuser/uploads
sudo chown sftpuser:sftpuser /home/sftpuser/uploads
sudo chmod 755 /home/sftpuser/uploads
3. 방화벽 설정
# UFW (Ubuntu)
# SFTP (SSH)
sudo ufw allow 22/tcp
# FTPS (Explicit)
sudo ufw allow 21/tcp
sudo ufw allow 60000:60100/tcp
# FTPS (Implicit)
sudo ufw allow 990/tcp
# SMB
sudo ufw allow 445/tcp
sudo ufw allow 139/tcp
# NFS
sudo ufw allow 2049/tcp
# 특정 IP만 허용
sudo ufw allow from 192.168.1.0/24 to any port 22
성능 최적화
1. 대용량 파일 전송
# rsync 최적화
rsync -avz \
--compress-level=1 \
--partial \
--progress \
--bwlimit=10000 \
--timeout=300 \
large_file.zip [email protected]:/path/
# SCP 압축 비활성화 (이미 압축된 파일)
scp -o "Compression no" archive.zip [email protected]:/path/
# 병렬 전송 (GNU Parallel)
find . -type f | parallel -j 4 scp {} [email protected]:/path/{}
2. 네트워크 튜닝
# TCP 윈도우 크기 증가
sudo sysctl -w net.ipv4.tcp_window_scaling=1
sudo sysctl -w net.core.rmem_max=134217728
sudo sysctl -w net.core.wmem_max=134217728
sudo sysctl -w net.ipv4.tcp_rmem="4096 87380 67108864"
sudo sysctl -w net.ipv4.tcp_wmem="4096 65536 67108864"
# /etc/sysctl.conf에 영구 적용
net.ipv4.tcp_window_scaling = 1
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
3. SMB 성능 최적화
# /etc/samba/smb.conf
[global]
# 성능 최적화
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072
read raw = yes
write raw = yes
max xmit = 65535
dead time = 15
getwd cache = yes
# SMB 3.0 멀티채널
server multi channel support = yes
# 비동기 I/O
aio read size = 16384
aio write size = 16384
문제 해결
문제 1: FTP Passive 모드 연결 실패
# 증상
ftp> ls
425 Failed to establish connection
# 원인: 방화벽이 데이터 포트 차단
# 해결: 서버에서 Passive 포트 범위 설정
# /etc/vsftpd.conf
pasv_enable=YES
pasv_min_port=60000
pasv_max_port=60100
# 방화벽 허용
sudo ufw allow 60000:60100/tcp
# 재시작
sudo systemctl restart vsftpd
문제 2: SFTP 권한 거부
# 증상
sftp> put file.txt
Couldn't open file "/file.txt": Permission denied
# 원인: Chroot 디렉토리 권한 문제
# 해결: 상위 디렉토리는 root 소유, 하위는 사용자 소유
sudo chown root:root /home/sftpuser
sudo chmod 755 /home/sftpuser
sudo chown sftpuser:sftpuser /home/sftpuser/uploads
sudo chmod 755 /home/sftpuser/uploads
문제 3: SMB 느린 전송 속도
# 증상: 10MB/s 이하의 느린 속도
# 원인 1: SMB 1.0 사용 (레거시)
# 해결: SMB 2/3 강제
# /etc/samba/smb.conf
min protocol = SMB2
max protocol = SMB3
# 원인 2: 작은 MTU
# 해결: MTU 확인 및 증가
ip link show eth0
sudo ip link set eth0 mtu 9000
# 원인 3: TCP 윈도우 크기
# 해결: 위 네트워크 튜닝 섹션 참고
문제 4: NFS Stale File Handle
# 증상
ls: cannot access '/mnt/nfs': Stale file handle
# 원인: NFS 서버 재시작 또는 네트워크 끊김
# 해결 1: 언마운트 후 재마운트
sudo umount -f /mnt/nfs
sudo mount /mnt/nfs
# 해결 2: 강제 언마운트
sudo umount -l /mnt/nfs
# 예방: hard 마운트 + intr 옵션
# /etc/fstab
192.168.1.100:/srv/nfs /mnt/nfs nfs hard,intr,timeo=600 0 0
프로토콜 선택 가이드
의사 결정 플로우차트
flowchart TD
Start[파일 전송 프로토콜 선택] --> Q1{용도는?}
Q1 -->|파일 전송| Q2{보안 필요?}
Q1 -->|파일 공유| Q3{OS는?}
Q1 -->|백업/동기화| RSYNC["✅ rsync\n증분 전송"]
Q2 -->|Yes| Q4{기존 인프라는?}
Q2 -->|No| FTP["⚠️ FTP\n레거시만"]
Q4 -->|SSH 있음| SFTP["✅ SFTP\n가장 안전"]
Q4 -->|FTP 있음| FTPS["✅ FTPS\nTLS 추가"]
Q4 -->|새로 구축| SFTP
Q3 -->|Windows| SMB["✅ SMB/CIFS\n네트워크 드라이브"]
Q3 -->|Linux| NFS["✅ NFS\n고성능"]
Q3 -->|혼합| SMB2["✅ SMB\n크로스 플랫폼"]
사용 사례별 추천
✅ 웹 호스팅 파일 업로드:
→ SFTP (보안) 또는 rsync (자동화)
✅ 팀 파일 공유:
→ SMB (Windows) 또는 NFS (Linux)
✅ 서버 간 백업:
→ rsync over SSH (증분 백업)
✅ 레거시 시스템 연동:
→ FTPS (FTP 호환 + 보안)
✅ 대용량 미디어 파일:
→ NFS (성능) 또는 SMB 3.0 (멀티채널)
✅ 간단한 파일 복사:
→ SCP (한 번만 전송)
✅ CI/CD 파이프라인:
→ rsync 또는 SFTP (자동화)
✅ 클라우드 스토리지 동기화:
→ rclone (S3, GCS, Azure 지원)
고급 주제
rclone (클라우드 스토리지)
# rclone 설치
curl https://rclone.org/install.sh | sudo bash
# 설정
rclone config
# S3 동기화
rclone sync /local/path s3:bucket-name/path
# Google Drive
rclone sync /local/path gdrive:folder
# 양방향 동기화 (주의: 충돌 가능)
rclone bisync /local/path remote:path
# 암호화 래퍼
rclone sync /local/path crypt:encrypted-remote
SSHFS (SSH File System)
# SSHFS 설치
sudo apt install sshfs
# 마운트
sshfs [email protected]:/remote/path /mnt/sshfs
# 언마운트
fusermount -u /mnt/sshfs
# 옵션
sshfs -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 \
[email protected]:/remote /mnt/sshfs
WebDAV
# WebDAV 서버 (Nginx)
location /webdav {
root /srv/webdav;
client_body_temp_path /tmp;
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
dav_access user:rw group:rw all:r;
auth_basic "WebDAV";
auth_basic_user_file /etc/nginx/.htpasswd;
create_full_put_path on;
client_max_body_size 0;
}
# 클라이언트 (Linux)
sudo apt install davfs2
sudo mount -t davfs https://example.com/webdav /mnt/webdav
정리
프로토콜 선택 체크리스트
📋 FTP
- [ ] 보안이 중요하지 않음
- [ ] 레거시 시스템과 호환 필요
- [ ] 최대 성능 필요
- [ ] 내부 네트워크만 사용
📋 SFTP
- [ ] 보안이 최우선
- [ ] SSH 인프라 있음
- [ ] 방화벽 친화적 필요
- [ ] 공개키 인증 사용
📋 FTPS
- [ ] 기존 FTP 인프라 유지
- [ ] TLS 암호화 추가
- [ ] 레거시 클라이언트 지원
📋 SMB/CIFS
- [ ] Windows 파일 공유
- [ ] 네트워크 드라이브 필요
- [ ] 권한 관리 중요
- [ ] Active Directory 통합
📋 NFS
- [ ] Linux 서버 간 공유
- [ ] 고성능 필요
- [ ] 투명한 파일 시스템 접근
- [ ] 내부 네트워크
📋 rsync
- [ ] 파일 동기화
- [ ] 증분 백업
- [ ] 대역폭 절약
- [ ] 자동화 스크립트
핵심 명령어 치트시트
# FTP
ftp ftp.example.com
ftp> get file.txt
ftp> put file.txt
# SFTP
sftp [email protected]
sftp> get file.txt
sftp> put file.txt
# SCP
scp file.txt [email protected]:/path/
scp [email protected]:/path/file.txt ./
# rsync
rsync -avz localdir/ [email protected]:/remote/
rsync -avz --delete localdir/ [email protected]:/remote/
# SMB 마운트
sudo mount -t cifs //server/share /mnt/smb -o username=user
# NFS 마운트
sudo mount -t nfs server:/export /mnt/nfs
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. FTP, SFTP, FTPS, SMB/CIFS, NFS, SCP, rsync 등 파일 전송 및 공유 프로토콜의 동작 원리, 보안, 성능 비교. 실전 구현 예제와 프로덕션 배포까지 완벽 마스터. Start now. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
참고 자료
- RFC 959 - FTP
- RFC 4253 - SSH Protocol
- SMB Protocol Documentation
- NFS RFC 7530
- Samba Documentation
- rsync Manual 한 줄 요약: 보안이 중요하면 SFTP, Windows 공유는 SMB, Linux 서버는 NFS, 백업은 rsync를 사용하되, 각 프로토콜의 특성을 이해하고 요구사항에 맞게 선택하세요.
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「파일 전송 프로토콜 완벽 가이드 | FTP·SFTP·FTPS·SMB·NFS·SCP 비교」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「파일 전송 프로토콜 완벽 가이드 | FTP·SFTP·FTPS·SMB·NFS·SCP 비교」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- HTTP vs FTP vs SSH 프로토콜 비교 | 용도·보안·파일 전송 선택 가이드
- FTP 프로토콜 실전 활용 | Active·Passive·FTPS·SFTP와 파일 전송 운영
- C++ 고성능 RPC 시스템: gRPC와 Protocol Buffers를 이용한 마이크로서비스 구축
이 글에서 다루는 키워드 (관련 검색어)
FTP, SFTP, SMB, CIFS, NFS, SCP, rsync, 파일전송, 네트워크 등으로 검색하시면 이 글이 도움이 됩니다.