본문으로 건너뛰기
Previous
Next
파일 전송 프로토콜 완벽 가이드 | FTP·SFTP·FTPS·SMB·NFS·SCP 비교

파일 전송 프로토콜 완벽 가이드 | FTP·SFTP·FTPS·SMB·NFS·SCP 비교

파일 전송 프로토콜 완벽 가이드 | 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

특성FTPSFTP
프로토콜독립 프로토콜SSH 기반
포트21 (제어), 20 (데이터)22 (단일)
암호화❌ 평문✅ SSH 암호화
인증ID/PWID/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.01984레거시, 보안 취약점 다수
SMB 2.02006성능 개선, 파이프라이닝
SMB 2.12010대용량 MTU 지원
SMB 3.02012암호화, 멀티채널, RDMA
SMB 3.1.12015AES-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 버전

버전특징
NFSv3UDP/TCP, Stateless, 널리 사용
NFSv4TCP만, Stateful, 방화벽 친화적 (단일 포트 2049), ACL 지원
NFSv4.1pNFS (병렬 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. 프로토콜 비교

종합 비교표

프로토콜포트암호화속도사용 사례장점단점
FTP21, 20⭐⭐⭐⭐⭐레거시 시스템빠름, 간단보안 취약
SFTP22✅ SSH⭐⭐⭐⭐안전한 파일 전송보안, 방화벽 친화적약간 느림
FTPS21, 990✅ TLS⭐⭐⭐⭐레거시 FTP 보안FTP 호환복잡한 방화벽
SMB445✅ (v3+)⭐⭐⭐⭐Windows 공유권한 관리 강력Windows 중심
NFS2049✅ (v4+)⭐⭐⭐⭐⭐Linux 서버 공유빠름, 투명Unix 중심
SCP22✅ SSH⭐⭐⭐간단한 파일 복사간단, 안전동기화 불가
rsync22, 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와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.

참고 자료

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「파일 전송 프로토콜 완벽 가이드 | 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 비교」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 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 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

FTP, SFTP, SMB, CIFS, NFS, SCP, rsync, 파일전송, 네트워크 등으로 검색하시면 이 글이 도움이 됩니다.