ftp.exeは パッシブモード(PASV/EPSV)に対応していません。quote pasvを送れてもクライアント側はアクティブのままです。curlを使いましょう。curlはデフォルトでパッシブ。必要なら--ftp-pasv(明示)、--disable-epsv、--ftp-skip-pasv-ipなどで挙動を調整できます。ftp.exeではダメなのかFTPは 制御用(通常21/tcp) と データ転送用 の2本のTCPコネクションを使うプロトコルです。データ側コネクションを「誰が張るか」でアクティブとパッシブに分かれます。
PORTを通知し、サーバー→クライアント にデータ接続を張ってくる。クライアント側FW/NATで止まりがち。PASV/EPSVを送り、クライアント→サーバー にデータ接続を張る。FW/NAT下でも通りやすく、現在の主流。Windows標準のftp.exeはパッシブモード非対応であるため、quote pasvでサーバー側をPASV状態にしてもクライアントは結局PORTを投げることになります。そのため、FW/NAT越しの一般的なサーバーでは、ディレクトリ一覧や転送が失敗しやすいという実害につながります。
-passive=onで明示可能。一番早い解決法。curlは別名エイリアスなのでcurl.exeとして呼ぶのが確実)、デフォルトがパッシブ。補足(SFTP/FTPSの話) FTPは平文プロトコルです。機微情報には FTPS または SFTP を推奨します。なお、Windows同梱のcurlはSFTPが無効化されているビルドなので、SFTPが必要なら公式ビルドやWinSCPを検討してください。
curlでやるFTPWindowsにおいては、WinSCPやFFFTPなどのソフトウェアをインストールしてFTPを利用するのが簡単かつ便利です。が、「とりあえずFTPの接続確認だけできればいい」という場合は、わざわざインストールしなくてもcurlを使うことで実行できます。
以降の例はパッシブモードで動きます。curlにおいて、ユーザー名/パスワードは--userで指定します。
REM PowerShellなら 'curl' はInvoke-WebRequestのエイリアス。
REM 紛らわしい場合は常に 'curl.exe' を使う。
curl.exe --versioncurl.exe -u USER:PASSWORD ftp://ftp.example.com/> listing.txtで保存。$ 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# 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 --:--:-- 3REM 単一ファイル
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はリモート側のパスを作成してから転送します。
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.txtcurl.exe --use-ascii -T .\readme.txt -u USER:PASSWORD ftp://ftp.example.com/--use-asciiを利用。curlは既定でEPSV→PASVの順に試行し、パッシブ接続を張ります。挙動がおかしいときは、次のスイッチが有効です。
EPSVを無効化(古い/特殊なサーバー対策)
curl.exe --disable-epsv -v -u USER:PASSWORD ftp://ftp.example.com/PASV応答に含まれるIPが“おかしい”とき(NAT誤設定) サーバーがプライベートIPを返す例では、制御接続と同じIPを使う オプションが効きます。
curl.exe --ftp-skip-pasv-ip -v -u USER:PASSWORD ftp://ftp.example.com/典型的なエラー “425 Can’t open data connection”
--disable-epsv/--ftp-skip-pasv-ipを試す。サーバー側ではパッシブ用のポートレンジ開放と、NAT配下なら外向きIPの通知設定(例:pasv_address等)が必要です。
--user USER:PASSWORDを書くのは避け、.netrc(--netrc/--netrc-file)の利用や、CIのシークレット/環境変数から差し込むのが安全です。(.netrcの詳細は curl 一般機能の話のため割愛)@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を。