頑張らないために頑張る

ゆるく頑張ります

Windows標準のftp.exeはパッシブモード非対応。だからこそWinSCPやcurlでFTPを使う

Posted at — Mar 4, 2026

TL;DR

なぜftp.exeではダメなのか

FTPは 制御用(通常21/tcp) と データ転送用 の2本のTCPコネクションを使うプロトコルです。データ側コネクションを「誰が張るか」でアクティブとパッシブに分かれます。

Windows標準のftp.exeはパッシブモード非対応であるため、quote pasvでサーバー側をPASV状態にしてもクライアントは結局PORTを投げることになります。そのため、FW/NAT越しの一般的なサーバーでは、ディレクトリ一覧や転送が失敗しやすいという実害につながります。

代替手段の選択肢

補足(SFTP/FTPSの話) FTPは平文プロトコルです。機微情報には FTPS または SFTP を推奨します。なお、Windows同梱のcurlはSFTPが無効化されているビルドなので、SFTPが必要なら公式ビルドやWinSCPを検討してください。

curlでやるFTP

Windowsにおいては、WinSCPやFFFTPなどのソフトウェアをインストールしてFTPを利用するのが簡単かつ便利です。が、「とりあえずFTPの接続確認だけできればいい」という場合は、わざわざインストールしなくてもcurlを使うことで実行できます。

以降の例はパッシブモードで動きます。curlにおいて、ユーザー名/パスワードは--userで指定します。

0) 事前チェック(Windowsでの呼び出しとバージョン)

REM PowerShellなら 'curl' はInvoke-WebRequestのエイリアス。
REM 紛らわしい場合は常に 'curl.exe' を使う。
curl.exe --version

1) ディレクトリ一覧を取得(LIST)

curl.exe -u USER:PASSWORD ftp://ftp.example.com/
$ curl -u myuser:mypassword ftp://localhost/
-rw-------    1 ftp      ftp            15 Feb 25 05:58 foo.txt
-rw-r--r--    1 ftp      ftp            15 Feb 25 05:06 hoge.txt

詳細ログが欲しいとき(トラブル調査やPASV応答の確認):

curl.exe -v -u USER:PASSWORD ftp://ftp.example.com/path/

--vで PASV/EPSV のやり取りや応答コードが見えます。

$ curl -v -u myuser:mypassword ftp://localhost/
* Host localhost:21 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:21...
* Established connection to localhost (::1 port 21) from ::1 port 64029
< 220 (vsFTPd 3.0.2)
> USER myuser
< 331 Please specify the password.
> PASS mypassword
< 230 Login successful.
> PWD
< 257 "/"
* Entry path is '/'
* Request has same path as previous transfer
> EPSV
* Connect data stream passively
< 229 Entering Extended Passive Mode (|||21110|).
* Connecting to ::1 (::1) port 21110
*   Trying [::1]:21110...
* Established 2nd connection to localhost (::1 port 21110) from ::1 port 64020
> TYPE A
< 200 Switching to ASCII mode.
> LIST
< 150 Here comes the directory listing.
* Maxdownload = -1
-rw-------    1 ftp      ftp            15 Feb 25 05:22 foo.txt
-rw-r--r--    1 ftp      ftp            15 Feb 25 05:06 hoge.txt
* abort upload
* Remembering we are in dir ""
< 226 Directory send OK.
* Connection #0 to host localhost:21 left intact

2) ファイルをダウンロード(RETR)

# REM リモート名で保存(-O / --remote-name)
curl.exe -O -u USER:PASSWORD ftp://ftp.example.com/path/file.zip

--Oを付けるとリモート名で保存。付けない場合は内容が標準出力へ。

$ curl -O -u myuser:mypassword ftp://localhost/hoge.txt
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    15  100    15    0     0      2      0  0:00:07  0:00:07 --:--:--     3

3) ファイルをアップロード(STOR)

REM 単一ファイル
curl.exe -T .\report.csv -u USER:PASSWORD ftp://ftp.example.com/upload/

REM 複数ファイル(ブレース展開)
curl.exe -T "{a.txt,b.txt}" -u USER:PASSWORD ftp://ftp.example.com/upload/

--T/--upload-fileが基本。URLがディレクトリで終わるとローカル名をそのまま使います。複数指定や連番の一括アップロードにも対応。

$ curl -T foo.txt --user myuser:mypassword ftp://localhost/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    15    0     0  100    15      0      2  0:00:07  0:00:07 --:--:--     3

必要ならサーバー側に中間ディレクトリを自動作成:

curl.exe --ftp-create-dirs -T ".\build\artifact.bin" -u USER:PASSWORD ^
  ftp://ftp.example.com/releases/2026/02/

---ftp-create-dirsはリモート側のパスを作成してから転送します。

4) ファイルの削除・リネーム(FTPコマンドを直接発行)

REM 削除(DELE)
curl.exe -u USER:PASSWORD -Q "DELE /path/old.log" ftp://ftp.example.com/

REM リネーム(RNFR/RNTO)
curl.exe -u USER:PASSWORD ^
  -Q "RNFR /path/oldname.txt" -Q "RNTO /path/newname.txt" ^
  ftp://ftp.example.com/

--Q/--quoteで任意のFTPコマンドを送れます(DELE/MKD/RMD/RNFR/RNTO など)。

curl -u myuser:mypassword -Q "RNFR foo.txt" -Q "RNTO noname.txt" ftp://localhost/
-rw-r--r--    1 ftp      ftp            15 Feb 25 05:06 hoge.txt
-rw-------    1 ftp      ftp            15 Feb 25 05:58 noname.txt
$ curl -u myuser:mypassword -Q "DELE noname.txt" ftp://localhost/
-rw-r--r--    1 ftp      ftp            15 Feb 25 05:06 hoge.txt

5) テキスト転送をASCIIで行いたい(TYPE A)

curl.exe --use-ascii -T .\readme.txt -u USER:PASSWORD ftp://ftp.example.com/

備考

実戦テクとトラブル対処

curlは既定でEPSV→PASVの順に試行し、パッシブ接続を張ります。挙動がおかしいときは、次のスイッチが有効です。

認証情報の扱い

バッチの雛形(再実行に強い・ログ付き)

@echo off
setlocal enabledelayedexpansion

set HOST=ftp.example.com
set USER=myuser
set PASS=mypassword
set REMOTE_DIR=/incoming
set LOCAL_DIR=%~dp0out
set LOG=%~dp0logs\ftp-%DATE:~0,10%.log

mkdir "%LOCAL_DIR%" "%~dp0logs" 2>nul

REM 1) 一覧 + 2) ダウンロード + 3) アップロード
(
  echo === LIST ===
  curl.exe -sS -v -u %USER%:%PASS% ftp://%HOST%%REMOTE_DIR%/

  echo === DOWNLOAD ===
  curl.exe -sS -O -u %USER%:%PASS% ftp://%HOST%%REMOTE_DIR%/spec.pdf

  echo === UPLOAD ===
  curl.exe -sS --ftp-create-dirs -T "%LOCAL_DIR%\report.csv" -u %USER%:%PASS% ^
    ftp://%HOST%%REMOTE_DIR%/
) 1>>"%LOG%" 2>&1

if errorlevel 1 (
  echo [ERROR] 転送に失敗しました。ログを確認してください: %LOG%
  exit /b 1
) else (
  echo [OK] 転送に成功しました。ログ: %LOG%
)

--sSで静かに(失敗時はメッセージ)、-vを併用すると問題切り分けが容易。

まとめ

-ftp.exeはパッシブ非対応。PASV/EPSVが必須の現場では実用になりません。 - curlはデフォルトでパッシブ。--disable-epsv--ftp-skip-pasv-ip等のスイッチで相性問題に対応できます。 - Windows 10/11ならcurl.exeが標準で使える(PowerShellのエイリアスに注意)。SFTPが必要なら公式ビルドやWinSCPを。

参考

  1. 【Windows】結論 ftp.exe は Passive モードに対応していない!
  2. WindowsのFTPクライアントではパッシブモード(PASV)では接続できない?
  3. FTP操作はcURLで行うと便利です
  4. How to use passive FTP mode in Windows command prompt?
  5. curl shipped by Microsoft
comments powered by Disqus