Web制作#curl FTPS アップロード 0バイト

curlのFTPSアップロードが0バイトになる時の直し方|本番フォームが約3分止まった例とPythonでの代わりの方法【2026年】

小さなオフィスを斜め上から見た図で、担当者が届いた空の荷物を秤に載せ、隣で別の担当者が中身の詰まった箱を送り直している
イラスト:AIで作成

この記事の結論

curlのFTPSアップロードで0バイトになるなら、Pythonのftplibに替えて、置いた後にサイズを照合する形が確実です。僕の会社では curl -T で本番フォームの処理ファイルが0バイトになり、約3分止まりました。

目次6項目
  1. curlのFTPSアップロードで0バイトになるとき:終了コード18と「空のファイル」の2つの出方
  2. curlの0バイト事故の対処手順:Pythonのftplibで置き直し、サイズを照合する
  3. 裏方で実際に起きた例:フォームの処理ファイルが0バイトになり、約3分止まった2026年7月
  4. FTPSアップロードの0バイトを再発させない:サイズ照合とHTTPの確認をスクリプトに入れる
  5. 業務でFTPSのアップロードをスクリプトやAIに任せるときの注意
  6. よくある質問

SNS運用・広告・採用マーケティングの無料相談はこちら →

curlのFTPSアップロードで0バイトになったとき、僕の会社で効いたのは原因の深追いではなく、Pythonのftplibに替えて、置いた後にサイズを照合することでした。2026年7月には、curl -T で本番のフォームの処理ファイルが0バイトになり、広告を出していたLPのフォームが約3分止まっています。

終了コード18の意味と、ftplibで置き直す手順、事故を見つける確認の仕方を順に書きました。「コマンドは終わったのに、サーバーのファイルが空」という方は、まず次の章の表から見てください。

curlのFTPSアップロードで0バイトになるとき:終了コード18と「空のファイル」の2つの出方

curlのFTPSアップロードの失敗には、僕の会社の記録では2つの出方がありました。途中で止まって終了コード18を返す形と、サーバー上のファイルが0バイトになる形です。

FTPSとは、ファイルを送るFTPの通信を、SSL/TLSで暗号化して行う方式のことです。レンタルサーバーへファイルを置くときによく使われ、curlでもPythonでも扱えます。

終了コードとは、コマンドが終わったときに返す、成功か失敗かを表す番号のことです。curlの公式のマニュアルでは、0以外の番号ごとに失敗の種類が決まっています。

僕の会社で出た18は、公式のマニュアルで「Partial file」とされています。ファイルの一部しか送れなかった、という意味です。libcurlのエラー一覧でも、転送が想定より短いか長かった、と説明されています。

FTPのアップロードで近い番号を、公式の説明と並べます。どれが出たかで、次に見る場所が変わります。

curlの終了コード公式の説明(要約)アップロードで見ること
18ファイルの一部しか送れなかったサーバー上のファイルが途中までか空になっていないか
25アップロードを始められなかった。FTPではサーバーがSTORを拒んだことが多い置き先のフォルダの権限・空き容量
64求めたFTPのSSLのレベルを満たせなかったサーバーがFTPSに対応しているか・設定
9ログインかフォルダへのアクセスを拒まれたアカウントと置き先のパス

僕の会社で困ったのは、18よりも「空のファイル」の方でした。番号が出れば失敗に気づけますが、空のファイルはサーバー上に残り、そのまま本番で使われます。

なぜ0バイトになったのか、通信のどこで欠けたのかまでは、僕の会社では突き止めていません。記録にあるのは、curlでは失敗し、Pythonのftplibでは同じサーバーに置けた、という事実です。この記事では、その範囲で書きます。

curlの0バイト事故の対処手順:Pythonのftplibで置き直し、サイズを照合する

本番のファイルが0バイトになったら、まず手元の正しいファイルを置き直してサービスを戻します。そのうえで、アップロードの手段をPythonのftplibに替え、置くたびにサイズを照合します。

  1. 本番のURLを開き、止まっているかを確かめる。 フォームの処理ファイルなら、送信が通るかを見ます。あわせて、サーバー上のサイズが0になっていないかを確かめます。
  2. 手元の正しいファイルを、ftplibで置き直す。 下のコードの形です。curlで置き直すと、同じ事故を重ねる恐れがあります。
  3. サーバー上のサイズと手元のサイズが同じかを確かめる。 違えば、もう一度置きます。
  4. 本番のURLのHTTPステータスを確かめる。 ページなら200が返るかを見ます。
  5. 以後のアップロードを、同じ手順のスクリプトに替える。 curlは、ダウンロードや一覧の取得だけに使います。

Pythonのftplibで、FTPSで1ファイルを置く形は次のとおりです。ホスト名・ユーザー名・フォルダ・ファイル名は例です。

import ftplib, os, ssl

host = "ftp.example.com"
user = "user"
password = os.environ["FTP_PASSWORD"]   # パスワードはコードに直接書かない
name = "index.html"

ctx = ssl.create_default_context()
ftp = ftplib.FTP_TLS(host, context=ctx, timeout=60)
ftp.login(user, password)
ftp.prot_p()                  # データの接続も暗号化する
ftp.cwd("public_html")
with open(name, "rb") as fh:
    ftp.storbinary("STOR " + name, fh)

remote = ftp.size(name)
local = os.path.getsize(name)
ftp.quit()
if remote != local:
    raise SystemExit(f"サイズ不一致: local={local} remote={remote}")
print("OK", local)

FTP_TLS は、FTPにTLSを足すPythonの標準の部品です。Pythonの公式ドキュメントには、データの接続は利用者が prot_p() を呼んで明示的に守る必要がある、と注意が書かれています。

storbinary() は、ファイルをバイナリのまま送る命令です。公式ドキュメントでは、渡すファイルはバイナリで開いたものとされているので、"rb"(バイナリで読む)で開きます。

size() は、サーバー上のファイルの大きさを聞く命令です。公式ドキュメントでは、この SIZE という命令は標準化されておらず、多くのサーバーが対応している、という扱いです。

返り値が None なら、照合できなかったものとして失敗に数えます。

裏方で実際に起きた例:フォームの処理ファイルが0バイトになり、約3分止まった2026年7月

ここからは、僕の会社(株式会社UrakaTA)の運用記録からの転記です。お客さんの案件なので、サイト・サーバー・ファイルの場所が分かる情報は伏せています。

2026年7月29日、広告を出していたLPのフォームのファイルを、本番のサーバーへ反映しようとしました。サーバーはレンタルサーバーで、置き方はFTPSです。

反映には、curl -T にFTPSのオプションを付けたスクリプトを使いました。

この反映が失敗し、フォームの送信を受けるPHPのファイルが、本番で0バイトになりました。フォームの送信を受けるファイルが空なので、問い合わせを受けられない状態です。止まっていたのは約3分でした。

記録には、問い合わせを取りこぼす一歩手前だった、と残っています。広告から人が来ている最中のフォームだったからです。

まずかったのは、前日の7月28日にも、別のファイル(ページのHTML)の curl -T で同じ終了コード18を出していました。それなのに、手順をスクリプトにするとき、curlを使ってしまいました。

この件のあと、僕の会社では本番へのアップロードをPythonのftplib(FTP_TLS と prot_p())に限っています。置いた後には、サイズの照合と、本番URLのHTTPステータスの確認を必ず行います。

2026年8月にこのコラムを初めて公開したときも、アップロードはftplibで行いました。置いた21ファイルすべてで、手元とサーバーのサイズが一致することを確かめています。

FTPSアップロードの0バイトを再発させない:サイズ照合とHTTPの確認をスクリプトに入れる

再発を防ぐ要は、人の注意ではなく、スクリプトが自分で失敗を止める形にします。僕の会社では、次の3つを全部のアップロードの決まりにしています。

  • アップロードはftplibだけにする。 curlは、ダウンロード・ファイルの削除・一覧の取得には使ってよい、という線を引いています。
  • 置いた後に、サイズとHTTPの両方を確かめる。 どちらかが合わなければ、スクリプトを異常終了させます。
  • 動いている本番のファイルは、触る前にバックアップを取る。 空のファイルが残っても、すぐ戻せるようにするためです。

HTTPの確認で何を「正常」と見るかは、ファイルごとに決めておきます。僕の会社のフォームの処理ファイルは、ブラウザで開く(GET)と405を返す作りです。そのため405なら正常、ページなら200を正常としています。

確認合格不合格のときにすること
サイズの照合サーバー上のサイズが手元と同じもう一度置く。size() が None なら照合できていないので失敗に数える
HTTPステータスページは200・フォームの処理ファイルは作りどおりの番号手元のファイルとバックアップで戻す
終了の仕方2つとも合格のときだけ正常終了異常終了させ、後の処理(通知など)に進ませない

サイズの照合は、空のファイルを見つけるためのものです。終了コードが0でも、サーバー上のファイルが手元と違えば、それは成功ではありません。

この決まりは、手順書にも書いて残しています。たとえば僕の会社のお知らせ記事の更新の手順書には、本番への反映はftplibだけ、curl -Tは0バイト事故の実績あり、と書いてあります。

業務でFTPSのアップロードをスクリプトやAIに任せるときの注意

一度起きた事故は、手順書に書いてから次の作業に入る。 僕の会社の例では、前日に同じ失敗をしていたのに、手順をスクリプトにするときに繰り返しました。覚えているつもりでも、作り直す人やAIには伝わりません。

curlをFTPSで使うなら、--ssl ではなく --ssl-reqd を付ける。 curlの公式のマニュアルでは、--ssl は暗号化に対応しないサーバーだと暗号化なしの接続に戻ります。

--ftp-ssl はこの --ssl の旧名です。--ssl-reqd は、暗号化できなければ接続を打ち切ります。

パスワードをスクリプトに直接書かない。 上のコードのように、環境変数や、共有しない設定ファイルから読み込みます。スクリプトは手順書と一緒に共有されやすく、パスワードも一緒に広がるからです。

お客さんのサーバーでは、止まった時間と戻した時刻を記録して報告する。 数分でも、問い合わせのフォームが止まれば取りこぼしが出ることがあります。何が・いつからいつまで止まったかを残し、持ち主に伝えます。

置いたファイルが正しくても、サーバーの設定しだいで表示が変わることがあります。PHPのソースがそのまま表示された例は、.htaccess の SetHandler で PHP のソースが表示される原因に書きました。

管理画面から保存できずにFTPで置いた例は、WordPressの保存が403になりFTPで回避した手順にあります。

「コマンドは通ったのに本番が空になった」「アップロードの手順を安全に作り直したい」というときは、下のフォームからご相談ください。今の手順の確認から一緒に見ます。

よくある質問

curlの終了コード18は何を意味しますか?

ファイルの一部しか送れなかったという意味です。curlの公式のマニュアルでは「Partial file」とされています。アップロードでこれが出たら、サーバー上のファイルは途中までか空の可能性があるので、サイズを手元と比べてから次の作業に進みます。

curlの終了コードが0なら、アップロードは成功していますか?

終了コードだけでは判断しないのが安全です。僕の会社の記録では、curl -T のあとに本番のファイルが0バイトになっていました。置いた後にサーバー上のサイズを手元と比べ、ページのURLのHTTPステータスも確かめて、初めて成功と数えています。

curlをFTPSで使い続けたい場合、何に気をつければいいですか?

少なくとも --ssl ではなく --ssl-reqd を使います。curlの公式のマニュアルでは、--ssl(旧名 --ftp-ssl)はサーバーが対応していないと暗号化なしの接続に戻ります。そのうえで、置いた後のサイズの照合を必ず手順に入れてください。

Pythonのftplibで、ファイルの中身まで暗号化されますか?

FTP_TLS を使い、prot_p() を呼べば暗号化されます。Pythonの公式ドキュメントには、データの接続は利用者が prot_p() を呼んで明示的に守る必要がある、と書かれています。呼び忘れると、ログインは暗号化されても中身は暗号化されません。

引用元・参考にした記事

  1. ftplib — FTP protocol client出典Python documentation参照
  2. libcurl - Error Codes出典curl参照
  3. curl - How To Use出典curl参照

Let's Create
The Next Move.

マーケティング・SNS運用・採用のご相談はこちらから。