파일 전송 프로토콜 완벽 가이드 | 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
  2. SFTP
  3. FTPS
  4. SMB/CIFS
  5. NFS
  6. SCP와 rsync
  7. 프로토콜 비교
  8. 실전 구현

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

특성FTPSFTP
프로토콜독립 프로토콜SSH 기반
포트21 (제어), 20 (데이터)22 (단일)
암호화❌ 평문✅ SSH 암호화
인증ID/PWID/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.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<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 버전

버전특징
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<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

참고 자료

한 줄 요약: 보안이 중요하면 SFTP, Windows 공유는 SMB, Linux 서버는 NFS, 백업은 rsync를 사용하되, 각 프로토콜의 특성을 이해하고 요구사항에 맞게 선택하세요.

---
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3