メインコンテンツへスキップ
  1. Linux 関連記事一覧/
  2. Ubuntu 26.04 LTS Server/

Ubuntu 26.04 LTS Server の FTP サーバー設定(vsftpd)

目次

FTP サーバーを使う前に

FTP は利用者名・パスワード・ファイルの中身を暗号化せずに送ります。 インターネットに面したサーバーでは使わないでください。暗号化して同じことをするなら、SSH サーバーの SFTP / SCP か、この記事の後半で扱う FTPS を使います。

FTP(RFC 959)は制御用とデータ用の2本の TCP 接続を使います。制御接続はサーバーの 21 番で、ここにコマンドと応答が流れます。ファイルの中身とディレクトリ一覧は、そのつど張り直す別の接続を通ります。データ接続をどちら側から張るかで2通りあり、パッシブモードではサーバーが待ち受けたポートへクライアントが接続し、アクティブモードではサーバーからクライアントへ接続します。

パッケージ説明
vsftpd3.0.5main にある。この記事で使う
proftpd-core1.3.9universe
pure-ftpd1.0.50universe

この記事で編集するファイルは /etc/vsftpd.conf の1つだけです。 公開用のディレクトリ(/srv/ftp-users/)は自分で作ります。

導入状況を確認する

サーバーは入っていません。 設定ファイルも 21 番の待ち受けもありません。

サーバー側の確認 実行例
kazulog@sv1:~$ apt-cache policy vsftpd | head -3
vsftpd:
  Installed: (none)
  Candidate: 3.0.5-0.4
kazulog@sv1:~$ ls -l /etc/vsftpd.conf /srv/ftp 2>&1
ls: cannot access '/etc/vsftpd.conf': No such file or directory
ls: cannot access '/srv/ftp': No such file or directory
kazulog@sv1:~$ sudo ss -tlnp 'sport = :21'; echo "ss rc=$?"
State                  Recv-Q                  Send-Q                                   Local Address:Port                                   Peer Address:Port                 Process
ss rc=0

クライアントの ftp コマンドは最初から入っています(実体は tnftp)。curl も FTP と FTPS を話せます。

クライアント側の確認 実行例
kazulog@sv2:~$ which ftp curl
/usr/bin/ftp
/usr/bin/curl

FTP サーバーを導入する

導入コマンド
sudo apt install -y vsftpd

証明書を作る ssl-cert が一緒に入ります(後半の FTPS で使います)。

導入されるパッケージ 実行例
Installing:
  vsftpd

Installing dependencies:
  ssl-cert

Summary:
  Upgrading: 0, Installing: 2, Removing: 0, Not Upgrading: 96
  Download size: 141 kB
  Space needed: 385 kB / 63.1 GB available

導入した時点で有効化され、起動します。

導入の最後 実行例
Setting up ssl-cert (1.1.3ubuntu2) ...
Created symlink '/etc/systemd/system/multi-user.target.wants/ssl-cert.service''/usr/lib/systemd/system/ssl-cert.service'.
Setting up vsftpd (3.0.5-0.4) ...
Created symlink '/etc/systemd/system/multi-user.target.wants/vsftpd.service''/usr/lib/systemd/system/vsftpd.service'.
/usr/lib/tmpfiles.d/vsftpd.conf:1: Line references path below legacy directory /var/run/, updating /var/run/vsftpd/empty → /run/vsftpd/empty; please update the tmpfiles.d/ drop-in file accordingly.

21 番を待ち受けます。表示が *:21 なのは IPv6 のソケットで IPv4 も受けているためです(listen=NOlisten_ipv6=YES)。

導入直後の状態 実行例
kazulog@sv1:~$ systemctl status vsftpd --no-pager | head -6 | cat
● vsftpd.service - vsftpd FTP server
     Loaded: loaded (/usr/lib/systemd/system/vsftpd.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-09-12 10:16:30 JST; 7s ago
 Invocation: 6e752ef8a1d14ad8895116d39f89c1bf
    Process: 2759 ExecStartPre=/bin/mkdir -p /var/run/vsftpd/empty (code=exited, status=0/SUCCESS)
   Main PID: 2761 (vsftpd)
kazulog@sv1:~$ sudo ss -tlnp 'sport = :21'
State               Recv-Q              Send-Q                             Local Address:Port                             Peer Address:Port              Process
LISTEN              0                   32                                             *:21                                          *:*                  users:(("vsftpd",pid=2761,fd=3))              

設定ファイル(/etc/vsftpd.conf)

設定は /etc/vsftpd.conf の1ファイルです。コメントを除いた有効行は導入直後で13行あり、匿名接続は無効、システムの利用者はログインできる状態です。

有効行だけを見る 実行例
kazulog@sv1:~$ grep -vE '^(#|$)' /etc/vsftpd.conf
listen=NO
listen_ipv6=YES
anonymous_enable=NO
local_enable=YES
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
ssl_enable=NO

この記事で触るのは次の4か所です。write_enablelocal_umaskchroot_local_user# で無効にされた状態で置かれているので、その行を探して # を外すか、値を書き換えます。

編集する行の場所 実行例
kazulog@sv1:~$ grep -n 'write_enable\|local_umask\|chroot_local_user\|ssl_enable' /etc/vsftpd.conf
31:#write_enable=YES
35:#local_umask=022
44:#anon_mkdir_write_enable=YES
112:# the possible risks in this before using chroot_local_user or
114:#chroot_local_user=YES
117:# directory. If chroot_local_user is YES, then this list becomes a list of
122:#chroot_local_user=YES
151:ssl_enable=NO
設定既定意味
anonymous_enableNO有効行匿名(ftp 利用者)での接続
local_enableYES有効行システムの利用者でのログイン
write_enableコメント31書き込み系のコマンド全般。無いとアップロードできない
local_umaskコメント(=077)35アップロードされたファイルの権限
chroot_local_userコメント114 と 122利用者を自分のディレクトリに閉じ込める
ssl_enableNO151FTPS

chroot_local_user のコメント行は 114 行目と 122 行目の2か所にあります。 どちらを外しても同じですが、この記事ではファイルの末尾に追記する方式にします(関連する3行をまとめて置けるため)。

変更したら sudo systemctl restart vsftpd で反映します。なお /etc/ftpusers に書かれた利用者(root など)は FTP でログインできません。

ログインしてファイルを取得する

検証用の利用者を作ります。このパスワードは平文で流れることを示すための検証専用の文字列で、実際の運用では使いません。

検証用の利用者を作るコマンド
sudo useradd -m -s /bin/bash ftpdemo
echo 'ftpdemo:FtpIsPlaintext' | sudo chpasswd
echo "hello from ftp server sv1" | sudo -u ftpdemo tee /home/ftpdemo/hello.txt
検証用の利用者 実行例
kazulog@sv1:~$ sudo useradd -m -s /bin/bash ftpdemo
kazulog@sv1:~$ echo 'ftpdemo:FtpIsPlaintext' | sudo chpasswd
kazulog@sv1:~$ id ftpdemo
uid=1001(ftpdemo) gid=1001(ftpdemo) groups=1001(ftpdemo)
kazulog@sv1:~$ echo "hello from ftp server sv1" | sudo -u ftpdemo tee /home/ftpdemo/hello.txt

別のサーバーから ftp でログインします。現在地はホームの /home/ftpdemo で、get は成功し、put550 Permission denied. で失敗しますwrite_enable が無いため)。

ログインと取得 実行例
kazulog@sv2:~$ cd /tmp
kazulog@sv2:/tmp$ echo "uploaded from sv2" > upload.txt
kazulog@sv2:/tmp$ ftp 192.168.100.10
Connected to 192.168.100.10.
220 (vsFTPd 3.0.5)
Name (192.168.100.10:kazulog): ftpdemo
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
Remote directory: /home/ftpdemo
ftp> ls
229 Entering Extended Passive Mode (|||59134|)
150 Here comes the directory listing.
-rw-r--r--    1 1001     1001           26 Sep 12 10:16 hello.txt
226 Directory send OK.
ftp> get hello.txt
local: hello.txt remote: hello.txt
229 Entering Extended Passive Mode (|||53233|)
150 Opening BINARY mode data connection for hello.txt (26 bytes).
100% |***********************************************************************************************************************************************************|    26       60.88 KiB/s    00:00 ETA
226 Transfer complete.
26 bytes received in 00:00 (9.26 KiB/s)
ftp> put upload.txt
local: upload.txt remote: upload.txt
229 Entering Extended Passive Mode (|||47977|)
550 Permission denied.
ftp> bye
221 Goodbye.

229 Entering Extended Passive Mode はパッシブモードの応答で、括弧の中の数字がデータ接続のポートです。

アップロードを許可する(31行目)

編集するファイル
sudo vi /etc/vsftpd.conf

31 行目の # を外します。

/etc/vsftpd.conf の31行目(変更後)
write_enable=YES
変更後の確認 実行例
kazulog@sv1:~$ grep -n 'write_enable\|local_umask' /etc/vsftpd.conf
31:write_enable=YES
35:#local_umask=022
44:#anon_mkdir_write_enable=YES
kazulog@sv1:~$ sudo systemctl restart vsftpd

アップロードできるようになりますが、作られたファイルは -rw-------(600)です。 local_umask が無いときの既定が 077 のためです。

アップロード 実行例
kazulog@sv2:~$ cd /tmp
kazulog@sv2:/tmp$ ftp 192.168.100.10
Connected to 192.168.100.10.
220 (vsFTPd 3.0.5)
Name (192.168.100.10:kazulog): ftpdemo
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> put upload.txt
local: upload.txt remote: upload.txt
229 Entering Extended Passive Mode (|||52014|)
150 Ok to send data.
100% |***********************************************************************************************************************************************************|    18       29.15 KiB/s    00:00 ETA
226 Transfer complete.
18 bytes sent in 00:00 (3.69 KiB/s)
ftp> ls
229 Entering Extended Passive Mode (|||53689|)
150 Here comes the directory listing.
-rw-r--r--    1 1001     1001           26 Sep 12 10:16 hello.txt
-rw-------    1 1001     1001           18 Sep 12 10:20 upload.txt
226 Directory send OK.
ftp> bye
221 Goodbye.

アップロードされたファイルの権限を決める(35行目)

他の利用者にも読ませるなら 35 行目の # を外します。

編集するファイル
sudo vi /etc/vsftpd.conf
/etc/vsftpd.conf の35行目(変更後)
local_umask=022
変更後の確認 実行例
kazulog@sv1:~$ sudo ls -l /home/ftpdemo/
total 8
-rw-r--r-- 1 ftpdemo ftpdemo 26 Sep 12 10:16 hello.txt
-rw------- 1 ftpdemo ftpdemo 18 Sep 12 10:20 upload.txt
kazulog@sv1:~$ grep -n 'write_enable\|local_umask' /etc/vsftpd.conf
31:write_enable=YES
35:local_umask=022
44:#anon_mkdir_write_enable=YES
kazulog@sv1:~$ sudo systemctl restart vsftpd

設定後にアップロードしたファイルは -rw-r--r--(644)になります。先にアップロードしたファイルの権限は変わりません。

変更後のアップロード 実行例
kazulog@sv2:~$ cd /tmp
kazulog@sv2:/tmp$ ftp 192.168.100.10
Connected to 192.168.100.10.
220 (vsFTPd 3.0.5)
Name (192.168.100.10:kazulog): ftpdemo
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> put upload.txt upload2.txt
local: upload.txt remote: upload2.txt
229 Entering Extended Passive Mode (|||6067|)
150 Ok to send data.
100% |***********************************************************************************************************************************************************|    18       43.94 KiB/s    00:00 ETA
226 Transfer complete.
18 bytes sent in 00:00 (4.41 KiB/s)
ftp> ls
229 Entering Extended Passive Mode (|||21507|)
150 Here comes the directory listing.
-rw-r--r--    1 1001     1001           26 Sep 12 10:16 hello.txt
-rw-------    1 1001     1001           18 Sep 12 10:20 upload.txt
-rw-r--r--    1 1001     1001           18 Sep 12 10:21 upload2.txt
226 Directory send OK.
ftp> bye
221 Goodbye.

利用者を自分のディレクトリに閉じ込める(末尾に追記)

chroot_local_user=YES にすると、ログインした利用者は公開場所より上へ行けなくなります。ただし閉じ込め先の最上位ディレクトリに本人の書き込み権があると、vsftpd はログインを拒みます500 OOPS: vsftpd: refusing to run with writable root inside chroot())。ホームをそのまま使うと本人が書けてしまうので、最上位を root の持ち物にし、その下に書き込み用のディレクトリを置くのが定石です。

local_root で公開場所を変え、user_sub_token で利用者ごとに分けます。

編集するファイル
sudo vi /etc/vsftpd.conf
/etc/vsftpd.conf の末尾に追記
# 利用者を自分のディレクトリに閉じ込める
chroot_local_user=YES
user_sub_token=$USER
local_root=/srv/ftp-users/$USER

公開場所を作ります。最上位は root のまま、upload だけ本人の持ち物にします。

公開場所を作るコマンド
sudo mkdir -p /srv/ftp-users/ftpdemo/upload
sudo chown ftpdemo:ftpdemo /srv/ftp-users/ftpdemo/upload
echo "hello from ftp server sv1" | sudo tee /srv/ftp-users/ftpdemo/hello.txt
変更後の確認 実行例
kazulog@sv1:~$ tail -5 /etc/vsftpd.conf
#utf8_filesystem=YES
# 利用者を自分のディレクトリに閉じ込める
chroot_local_user=YES
user_sub_token=$USER
local_root=/srv/ftp-users/$USER
kazulog@sv1:~$ sudo ls -laR /srv/ftp-users/
/srv/ftp-users/:
total 12
drwxr-xr-x 3 root root 4096 Sep 12 10:22 .
drwxr-xr-x 4 root root 4096 Sep 12 10:22 ..
drwxr-xr-x 3 root root 4096 Sep 12 10:22 ftpdemo

/srv/ftp-users/ftpdemo:
total 16
drwxr-xr-x 3 root    root    4096 Sep 12 10:22 .
drwxr-xr-x 3 root    root    4096 Sep 12 10:22 ..
-rw-r--r-- 1 root    root      26 Sep 12 10:22 hello.txt
drwxr-xr-x 2 ftpdemo ftpdemo 4096 Sep 12 10:22 upload

/srv/ftp-users/ftpdemo/upload:
total 8
drwxr-xr-x 2 ftpdemo ftpdemo 4096 Sep 12 10:22 .
drwxr-xr-x 3 root    root    4096 Sep 12 10:22 ..
kazulog@sv1:~$ sudo systemctl restart vsftpd

ログイン後の現在地は / になり、上位には出られません。最上位には置けず(553)、書き込み用のディレクトリには置けます。

閉じ込めの確認 実行例
kazulog@sv2:~$ cd /tmp
kazulog@sv2:/tmp$ ftp 192.168.100.10
Connected to 192.168.100.10.
220 (vsFTPd 3.0.5)
Name (192.168.100.10:kazulog): ftpdemo
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pwd
Remote directory: /
ftp> ls
229 Entering Extended Passive Mode (|||47435|)
150 Here comes the directory listing.
-rw-r--r--    1 0        0              26 Sep 12 10:22 hello.txt
drwxr-xr-x    2 1001     1001         4096 Sep 12 10:22 upload
226 Directory send OK.
ftp> cd /home
550 Failed to change directory.
ftp> put upload.txt
local: upload.txt remote: upload.txt
229 Entering Extended Passive Mode (|||51762|)
553 Could not create file.
ftp> cd upload
250 Directory successfully changed.
ftp> put upload.txt
local: upload.txt remote: upload.txt
229 Entering Extended Passive Mode (|||31305|)
150 Ok to send data.
100% |***********************************************************************************************************************************************************|    18       36.02 KiB/s    00:00 ETA
226 Transfer complete.
18 bytes sent in 00:00 (3.83 KiB/s)
ftp> ls
229 Entering Extended Passive Mode (|||14734|)
150 Here comes the directory listing.
-rw-r--r--    1 1001     1001           18 Sep 12 10:23 upload.txt
226 Directory send OK.
ftp> pwd
Remote directory: /upload
ftp> bye
221 Goodbye.

暗号化する(FTPS、151行目)

151 行目を YES にすると FTPS(FTP over TLS、RFC 4217)になります。証明書は導入時に入った snakeoil のものが 149・150 行目で既に指定されています。

編集するファイル
sudo vi /etc/vsftpd.conf
/etc/vsftpd.conf の151行目(変更後)
ssl_enable=YES
変更後の確認 実行例
kazulog@sv1:~$ grep -n 'ssl_enable\|rsa_cert_file\|rsa_private_key_file' /etc/vsftpd.conf
149:rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
150:rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
151:ssl_enable=YES
kazulog@sv1:~$ sudo systemctl restart vsftpd

平文のログインは拒否されます。 tnftp は TLS に対応していないので、パスワードを聞かれる前に切られます。

平文のログインが拒否される 実行例
kazulog@sv2:~$ cd /tmp
kazulog@sv2:/tmp$ ftp 192.168.100.10
Connected to 192.168.100.10.
220 (vsFTPd 3.0.5)
Name (192.168.100.10:kazulog): ftpdemo
530 Non-anonymous sessions must use encryption.
ftp: Login failed
ftp> bye

FTPS のクライアントには curl を使います。--ssl-reqd で暗号化を必須にし、-k は snakeoil 証明書の検証を省くためのものです。

curl での FTPS 実行例
kazulog@sv2:~$ curl -sS -u ftpdemo:FtpIsPlaintext ftp://192.168.100.10/hello.txt; echo "rc=$?"
curl: (67) Access denied: 530
rc=67
kazulog@sv2:~$ curl -sS --ssl-reqd -k -u ftpdemo:FtpIsPlaintext ftp://192.168.100.10/hello.txt; echo "rc=$?"
hello from ftp server sv1
rc=0
kazulog@sv2:~$ curl -sS --ssl-reqd -k -T /tmp/upload.txt -u ftpdemo:FtpIsPlaintext ftp://192.168.100.10/upload/curl.txt; echo "rc=$?"
rc=0

設定ファイルの最終状態

ここまでの設定が入った状態です。触ったファイルは /etc/vsftpd.conf の1つだけで、導入直後の13行に5行が加わって18行になっています。

/etc/vsftpd.conf の有効行
kazulog@sv1:~$ grep -vE '^(#|$)' /etc/vsftpd.conf
listen=NO
listen_ipv6=YES
anonymous_enable=NO
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
use_localtime=YES
xferlog_enable=YES
connect_from_port_20=YES
secure_chroot_dir=/var/run/vsftpd/empty
pam_service_name=vsftpd
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
ssl_enable=YES
chroot_local_user=YES
user_sub_token=$USER
local_root=/srv/ftp-users/$USER

自分で作った公開場所と、サービスの状態です。最上位は root:rootupload だけが ftpdemo:ftpdemo になっています。

公開場所とサービスの状態
kazulog@sv1:~$ sudo ls -laR /srv/ftp-users/
/srv/ftp-users/:
total 12
drwxr-xr-x 3 root root 4096 Sep 12 10:22 .
drwxr-xr-x 4 root root 4096 Sep 12 10:22 ..
drwxr-xr-x 3 root root 4096 Sep 12 10:22 ftpdemo

/srv/ftp-users/ftpdemo:
total 16
drwxr-xr-x 3 root    root    4096 Sep 12 10:22 .
drwxr-xr-x 3 root    root    4096 Sep 12 10:22 ..
-rw-r--r-- 1 root    root      26 Sep 12 10:22 hello.txt
drwxr-xr-x 2 ftpdemo ftpdemo 4096 Sep 12 10:24 upload

/srv/ftp-users/ftpdemo/upload:
total 16
drwxr-xr-x 2 ftpdemo ftpdemo 4096 Sep 12 10:24 .
drwxr-xr-x 3 root    root    4096 Sep 12 10:22 ..
-rw-r--r-- 1 ftpdemo ftpdemo   18 Sep 12 10:24 curl.txt
-rw-r--r-- 1 ftpdemo ftpdemo   18 Sep 12 10:23 upload.txt
kazulog@sv1:~$ systemctl is-enabled vsftpd; systemctl is-active vsftpd
enabled
active
kazulog@sv1:~$ sudo ss -tlnp 'sport = :21'
State               Recv-Q              Send-Q                             Local Address:Port                             Peer Address:Port              Process
LISTEN              0                   32                                             *:21                                          *:*                  users:(("vsftpd",pid=5460,fd=3))              

平文で流れることを確かめる

sv1 とスイッチの間のリンクでキャプチャーすると、利用者名とパスワードがそのまま読めます。 最後の2回(No.277・No.292)は FTPS を有効にした後なので、USER だけで切られて PASS が出ていません。

USER と PASS 実行例
$ tshark -r ftp-server.pcap -n -Y 'ftp.request.command == "USER" || ftp.request.command == "PASS"' -T fields -e frame.number -e ip.src -e ftp.request.command -e ftp.request.arg
6	192.168.100.20	USER	ftpdemo
9	192.168.100.20	PASS	FtpIsPlaintext
77	192.168.100.20	USER	ftpdemo
80	192.168.100.20	PASS	FtpIsPlaintext
133	192.168.100.20	USER	ftpdemo
136	192.168.100.20	PASS	FtpIsPlaintext
189	192.168.100.20	USER	ftpdemo
192	192.168.100.20	PASS	FtpIsPlaintext
277	192.168.100.20	USER	ftpdemo
292	192.168.100.20	USER	ftpdemo

パスワードのパケットを16進で見ても、文字列がそのまま入っています。

No.9 の中身 実行例
$ tshark -r ubuntu-ftp-no9.pcap -n -x
0000  52 54 00 30 7e c3 52 54 00 78 56 37 08 00 45 10   RT.0~.RT.xV7..E.
0010  00 49 d0 db 40 00 40 06 20 54 c0 a8 64 14 c0 a8   .I..@.@. T..d...
0020  64 0a b8 84 00 15 af 9b d2 31 5a d4 22 ec 80 18   d........1Z."...
0030  00 fb e7 1e 00 00 01 01 08 0a 2d 4d d3 94 eb a6   ..........-M....
0040  1a d8 50 41 53 53 20 46 74 70 49 73 50 6c 61 69   ..PASS FtpIsPlai
0050  6e 74 65 78 74 0d 0a                              ntext..
上のtshark出力のパケット(No.9 PASS)のpcapをダウンロード

データ接続が別の TCP 接続であることも追えます。229 の応答で示された 53233 番へクライアントが接続し(No.42〜44)、そこをファイルの中身が流れて閉じます。

データ接続 実行例
$ tshark -r ftp-server.pcap -n -Y 'frame.number >= 40 && frame.number <= 52' -T fields -e frame.number -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e _ws.col.info
40	192.168.100.20	47236	192.168.100.10	21	Request: EPSV
41	192.168.100.10	21	192.168.100.20	47236	Response: 229 Entering Extended Passive Mode (|||53233|)
42	192.168.100.20	60442	192.168.100.10	53233	60442 → 53233 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM TSval=2184570297 TSecr=0 WS=256
43	192.168.100.10	53233	192.168.100.20	60442	53233 → 60442 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 MSS=1460 SACK_PERM TSval=2456829945 TSecr=2184570297 WS=256
44	192.168.100.20	60442	192.168.100.10	53233	60442 → 53233 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=2184570299 TSecr=2456829945
45	192.168.100.20	47236	192.168.100.10	21	Request: RETR hello.txt
46	192.168.100.10	21	192.168.100.20	47236	Response: 150 Opening BINARY mode data connection for hello.txt (26 bytes).
47	192.168.100.10	53233	192.168.100.20	60442	FTP Data: 26 bytes (EPASV) (RETR hello.txt)
48	192.168.100.10	53233	192.168.100.20	60442	53233 → 60442 [FIN, ACK] Seq=27 Ack=1 Win=65280 Len=0 TSval=2456829947 TSecr=2184570299
49	192.168.100.20	60442	192.168.100.10	53233	60442 → 53233 [ACK] Seq=1 Ack=27 Win=64256 Len=0 TSval=2184570301 TSecr=2456829947
50	192.168.100.20	60442	192.168.100.10	53233	60442 → 53233 [FIN, ACK] Seq=1 Ack=28 Win=64256 Len=0 TSval=2184570302 TSecr=2456829947
51	192.168.100.10	53233	192.168.100.20	60442	53233 → 60442 [ACK] Seq=28 Ack=2 Win=65280 Len=0 TSval=2456829950 TSecr=2184570302
52	192.168.100.10	21	192.168.100.20	47236	Response: 226 Transfer complete.

パッシブモードのポートは毎回変わります。 範囲を決めたい場合は pasv_min_portpasv_max_port を指定します。

パッシブモードのポート 実行例
$ tshark -r ftp-server.pcap -n -Y 'ftp.response.code == 229' -T fields -e frame.number -e ftp.response.arg
23	Entering Extended Passive Mode (|||59134|)
41	Entering Extended Passive Mode (|||53233|)
57	Entering Extended Passive Mode (|||47977|)
94	Entering Extended Passive Mode (|||52014|)
110	Entering Extended Passive Mode (|||53689|)
150	Entering Extended Passive Mode (|||6067|)
166	Entering Extended Passive Mode (|||21507|)
206	Entering Extended Passive Mode (|||47435|)
224	Entering Extended Passive Mode (|||51762|)
236	Entering Extended Passive Mode (|||31305|)
252	Entering Extended Passive Mode (|||14734|)

パスワードを含むフレームは平文のログイン4回分だけで、FTPS のログインは含まれません。

パスワードを含むフレーム 実行例
$ tshark -r ftp-server.pcap -n -Y 'frame contains "FtpIsPlaintext"' -T fields -e frame.number
9
80
136
192

TLS になった後のパケットを16進で見ても、読める文字列はありません。

No.346 の中身 実行例
$ tshark -r ubuntu-ftp-no346.pcap -n -x
0000  52 54 00 30 7e c3 52 54 00 78 56 37 08 00 45 00   RT.0~.RT.xV7..E.
0010  00 7e d6 cf 40 00 40 06 1a 3b c0 a8 64 14 c0 a8   .~..@.@..;..d...
0020  64 0a ab d0 5a c3 e3 d3 c3 78 be 6f 14 d9 80 18   d...Z....x.o....
0030  00 fb 1f fb 00 00 01 01 08 0a db 9a d4 4d 67 1f   .............Mg.
0040  1a ce 17 03 03 00 45 7d 26 01 1c 7d 16 17 0b 95   ......E}&..}....
0050  1f 8b de fa fa e4 77 8e 2c 45 83 69 76 18 ef 4f   ......w.,E.iv..O
0060  b5 d3 ac 11 c2 71 ec 56 a2 a1 c5 06 ce 27 d8 a8   .....q.V.....'..
0070  8a b9 19 27 2d b6 28 27 dc c9 1f ad d0 28 c6 10   ...'-.('.....(..
0080  37 c1 ef 9d 08 14 90 d4 30 00 73 6d               7.......0.sm
上のtshark出力のパケット(No.346 Application Data)のpcapをダウンロード

キャプチャーでは AUTH SSL234 Proceed with negotiation. が返り、以降は TLS になります。

AUTH と応答 実行例
$ tshark -r ftp-server.pcap -n -Y 'ftp.request.command == "AUTH" || ftp.response.code == 234' -T fields -e frame.number -e ip.src -e ftp.request.arg -e ftp.response.arg
303	192.168.100.20	SSL	
305	192.168.100.10		Proceed with negotiation.
369	192.168.100.20	SSL	
371	192.168.100.10		Proceed with negotiation.

ルータの設定を保存する

IOS XE のルータ R1 から copy コマンドで running-config をアップロードします。ルータ側は平文の FTP しか話さないので、151 行目を ssl_enable=NO に戻してから行います。

編集するファイル
sudo vi /etc/vsftpd.conf
FTPS を戻す 実行例
kazulog@sv1:~$ grep -n 'ssl_enable' /etc/vsftpd.conf
151:ssl_enable=NO
kazulog@sv1:~$ sudo systemctl restart vsftpd

ルータ側は利用者名とパスワードを ip ftp username / ip ftp password で設定します(確認の問い合わせを省くため file prompt quiet も入れています)。

R1 の設定コマンド
file prompt quiet
ip ftp username ftpdemo
ip ftp password FtpIsPlaintext

保存先の URL は閉じ込めた後の見え方に合わせます。 local_root=/srv/ftp-users/$USER なので、ルータから見た最上位が /srv/ftp-users/ftpdemo にあたり、書き込めるのは upload/ の下だけです。

R1 から設定を保存 実行例
R1#copy running-config ftp://192.168.100.10/upload/r1-confg
Writing upload/r1-confg !
4977 bytes copied in 3.419 secs (1456 bytes/sec)

サーバー側に届いています。

サーバー側 実行例
kazulog@sv1:~$ sudo ls -l /srv/ftp-users/ftpdemo/upload/
total 8
-rw-r--r-- 1 ftpdemo ftpdemo 18 Sep 12 10:24 curl.txt
-rw-r--r-- 1 ftpdemo ftpdemo 18 Sep 12 10:23 upload.txt
kazulog@sv1:~$ sudo ls -l /srv/ftp-users/ftpdemo/upload/
total 16
-rw-r--r-- 1 ftpdemo ftpdemo   18 Sep 12 10:24 curl.txt
-rw-r--r-- 1 ftpdemo ftpdemo 4977 Sep 12 10:30 r1-confg
-rw-r--r-- 1 ftpdemo ftpdemo   18 Sep 12 10:23 upload.txt
kazulog@sv1:~$ sudo head -3 /srv/ftp-users/ftpdemo/upload/r1-confg
!
! Last configuration change at 01:29:39 UTC Sat Sep 12 2026

確認できたら、ルータ側の一時的な設定は no ip ftp username / no ip ftp password / no file prompt quiet で戻します。

削除する

削除コマンド
sudo apt purge -y vsftpd

/etc/vsftpd.conf は消えますが、ftp 利用者と自分で作った /srv/ftp-users/(アップロードされたファイルを含む)は残ります。 依存で入った ssl-cert も残ります。

削除 実行例
kazulog@sv1:~$ sudo apt purge -y vsftpd | cat
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.

Reading package lists...
Building dependency tree...
Reading state information...
Solving dependencies...
The following package was automatically installed and is no longer required:
  ssl-cert
Use 'sudo apt autoremove' to remove it.

REMOVING:
  vsftpd*

Summary:
  Upgrading: 0, Installing: 0, Removing: 1, Not Upgrading: 96
  Freed space: 316 kB

(Reading database ... 91464 files and directories currently installed.)
Removing vsftpd (3.0.5-0.4) ...
Processing triggers for man-db (2.13.1-1build1) ...
(Reading database ... 91413 files and directories currently installed.)
Purging configuration files for vsftpd (3.0.5-0.4) ...
kazulog@sv1:~$ dpkg -l vsftpd 2>&1 | tail -1
dpkg-query: no packages found matching vsftpd
kazulog@sv1:~$ ls -l /etc/vsftpd.conf 2>&1
ls: cannot access '/etc/vsftpd.conf': No such file or directory
kazulog@sv1:~$ sudo ls -ld /srv/ftp /srv/ftp-users/ftpdemo/upload 2>&1
ls: cannot access '/srv/ftp': No such file or directory
drwxr-xr-x 2 ftpdemo ftpdemo 4096 Sep 12 10:30 /srv/ftp-users/ftpdemo/upload
kazulog@sv1:~$ getent passwd ftp
ftp:x:103:110:ftp daemon:/srv/ftp:/usr/sbin/nologin
kazulog@sv1:~$ sudo ss -tlnp 'sport = :21'; echo "ss rc=$?"
State                  Recv-Q                  Send-Q                                   Local Address:Port                                   Peer Address:Port                 Process
ss rc=0

公開場所ごと消すなら sudo userdel -r ftpdemosudo rm -rf /srv/ftp-users を別に実行します。

検証環境と実行ログ

CML 上に Ubuntu 26.04 LTS Server 2台と IOS XE のルータ1台を置いて採取しました。Ubuntu のディスクは wipe した直後の状態から始めています(前の検証で入れたパッケージが残っていると手順が再現しないため)。

検証構成
  sv1                         sv2                         R1
  ens3 192.168.100.10/24      ens3 192.168.100.20/24      Gi2 192.168.100.1/24
  vsftpd (TCP 21)             ftp / curl                  IOS XE 17.03.08a
   |                           |                           |
   +--------- LAB-SW ----------+---------------------------+
   ^ このリンクでキャプチャー(sv2 との TCP のみ)

キャプチャー全体のファイルです。

FTP と FTPS のキャプチャー(ubuntu-ftp.pcap)

..._ftp.txt は sv2 での ftp コマンドの操作記録です。

手順sv1sv2R1
初期状態(wipe 直後)show / conf / logshow / conf / log
導入(/etc/vsftpd.conf ができる)show / conf / log
検証用の利用者を作るshow / conf / log
ログインと取得(put は 550)show / conf / log / ftp
31行目 write_enableshow / conf / logftp
35行目 local_umaskshow / conf / logftp
末尾に追記(chroot と local_root)show / conf / logftp
151行目 ssl_enable(FTPS)show / conf / logshow / conf / log / ftp
設定ファイルの最終状態show / conf / log
FTPS を戻してルータから保存show / conf / logshow
削除(最終状態)show / conf / log
キャプチャーの解析tshark

参考リンク

Ubuntu Server: Set up an FTP server

RFC 959 - File Transfer Protocol

関連記事

Ubuntu 公式ページ