파일 전송 프로토콜 완벽 가이드 | 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<br/>Port 21
participant DS as Data<br/>Port 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<br/>IP: 192.168.1.100]
CP[Control Port<br/>Random: 50000]
DP[Data Port<br/>Random: 50001]
end
subgraph Server[Server<br/>IP: 203.0.113.1]
CS[Control Port<br/>21]
DS[Data Port<br/>20]
end
CP -->|1. Control Connection| CS
DS -->|2. Data Connection<br/>Server → Client| DP
style DS fill:#f96
style DP fill:#6cf
문제점: 클라이언트 방화벽이 서버의 연결을 차단할 수 있음
Passive Mode (수동 모드)
flowchart LR
subgraph Client[Client<br/>IP: 192.168.1.100]
CP[Control Port<br/>Random: 50000]
DP[Data Port<br/>Random: 50001]
end
subgraph Server[Server<br/>IP: 203.0.113.1]
CS[Control Port<br/>21]
DS[Data Port<br/>Random: 60000]
end
CP -->|1. Control Connection| CS
DP -->|2. Data Connection<br/>Client → 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[🔓 평문 전송<br/>ID/PW 노출]
I2[🔓 데이터 암호화 없음<br/>파일 내용 노출]
I3[🔓 중간자 공격<br/>Man-in-the-Middle]
I4[🔓 스니핑 가능<br/>Wireshark로 캡처]
end
subgraph Solutions[해결책]
S1[✅ SFTP 사용<br/>SSH 암호화]
S2[✅ FTPS 사용<br/>TLS 암호화]
S3[✅ VPN 터널링<br/>네트워크 암호화]
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<br/>Port 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<br/>Port 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<br/>Port 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<br/>Port 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<br/>평문 전송]
NFSv3[NFSv3<br/>기본 암호화 없음]
end
subgraph Secure[🔒 암호화 지원]
SFTP[SFTP<br/>SSH 암호화]
FTPS[FTPS<br/>TLS 암호화]
SMB3[SMB 3.0+<br/>AES 암호화]
NFSv4[NFSv4<br/>Kerberos]
SCP[SCP<br/>SSH 암호화]
RSYNC[rsync over SSH<br/>SSH 암호화]
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<br/>증분 전송]
Q2 -->|Yes| Q4{기존 인프라는?}
Q2 -->|No| FTP[⚠️ FTP<br/>레거시만]
Q4 -->|SSH 있음| SFTP[✅ SFTP<br/>가장 안전]
Q4 -->|FTP 있음| FTPS[✅ FTPS<br/>TLS 추가]
Q4 -->|새로 구축| SFTP
Q3 -->|Windows| SMB[✅ SMB/CIFS<br/>네트워크 드라이브]
Q3 -->|Linux| NFS[✅ NFS<br/>고성능]
Q3 -->|혼합| SMB2[✅ SMB<br/>크로스 플랫폼]
사용 사례별 추천
✅ 웹 호스팅 파일 업로드:
→ 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
참고 자료
- RFC 959 - FTP
- RFC 4253 - SSH Protocol
- SMB Protocol Documentation
- NFS RFC 7530
- Samba Documentation
- rsync Manual
한 줄 요약: 보안이 중요하면 SFTP, Windows 공유는 SMB, Linux 서버는 NFS, 백업은 rsync를 사용하되, 각 프로토콜의 특성을 이해하고 요구사항에 맞게 선택하세요.