2026年6月21日

2026年6月21日

PythonでHTTPSサーバーを立てる方法|http.serverとsslで開発用に動かす手順と本番の構成

Python では、標準ライブラリの http.server と ssl を組み合わせるだけで、HTTPS で応答するサーバーを数行で立てられます。ただしこれは開発やテスト用の手段で、公開サーバーとして使うものではありません。この記事では、自己署名証明書の作り方、ssl.SSLContext を使った正しい書き方、ブラウザや curl での確認方法、よくあるエラーの原因、そして本番で HTTPS を提供するときの構成(リバースプロキシで TLS を終端する方法)までを説明します。

まず用途を確認する

Python で HTTPS サーバーを立てたい場面は、大きく次の2つに分かれます。

  • 開発・テスト用:Webhook の受信テスト、HTTPS でないと動かないブラウザ機能(Service Worker、一部の位置情報 API など)の確認、社内での一時的なファイル共有など。
  • 本番のアプリを HTTPS で公開したい:Flask や FastAPI などで作ったアプリを外部に公開する場合。

前者なら標準ライブラリだけで足ります。後者は http.server を使わず、Nginx などのリバースプロキシで HTTPS を受け、裏のアプリサーバーに転送するのが一般的です。Python の公式ドキュメントでも http.server は本番利用を推奨しないと明記されています。

開発用の証明書を用意する

HTTPS には証明書と秘密鍵が必要です。手元で試すだけなら OpenSSL で自己署名証明書を作れます。

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout key.pem -out cert.pem -days 30 \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

-nodes は秘密鍵にパスフレーズを付けない指定で、サーバー起動時に入力を求められなくなります。その代わり key.pem は他人に読まれないよう権限を絞り(chmod 600 key.pem)、Git などに含めないでください。

subjectAltName を入れているのは、現在のブラウザや多くの TLS クライアントがホスト名の照合に CN ではなく SAN を使うためです。これを省くと、証明書を信頼させてもホスト名が一致しないというエラーになります。-addext は OpenSSL 1.1.1 以降で使えるオプションです。

ブラウザの警告を出さずに試したい場合は、ローカルに開発用の認証局を作って証明書を発行する mkcert のようなツールを使う方法もあります。どちらの場合も、作った証明書は開発用であり、外部の利用者向けには使えません。

標準ライブラリだけで HTTPS サーバーを立てる

カレントディレクトリのファイルを HTTPS で配信する最小の例です。

# https_server.py
import http.server
import ssl
from functools import partial

HOST = "127.0.0.1"
PORT = 4443

handler = partial(http.server.SimpleHTTPRequestHandler, directory="public")
httpd = http.server.ThreadingHTTPServer((HOST, PORT), handler)

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.load_cert_chain(certfile="cert.pem", keyfile="key.pem")

httpd.socket = context.wrap_socket(httpd.socket, server_side=True)

print(f"https://{HOST}:{PORT}/ で待ち受け中(Ctrl+C で停止)")
try:
    httpd.serve_forever()
except KeyboardInterrupt:
    pass
finally:
    httpd.server_close()

いくつか理由のある書き方をしています。

**ssl.SSLContext を使う。** 古い記事でよく見る ssl.wrap_socket() は長く非推奨扱いで、Python 3.12 で削除されました。今のバージョンでこの関数を呼ぶと AttributeError になります。SSLContext(ssl.PROTOCOL_TLS_SERVER) でサーバー用の設定を作り、wrap_socket() はコンテキストのメソッドとして呼びます。

TLS 1.2 未満を拒否する。 minimum_version で古いプロトコルを受け付けないようにしています。既定値でも古い方式はおおむね無効ですが、明示しておくと意図が伝わります。

配信するディレクトリを指定する。 SimpleHTTPRequestHandler は既定でカレントディレクトリをそのまま公開します。ホームディレクトリで起動すると、SSH の鍵や設定ファイルまで見えてしまうことがあるため、directory で専用のフォルダに限定しています。

**127.0.0.1 で待ち受ける。** 0.0.0.0 にすると同じネットワークの他の端末からも見えるようになります。他の端末から確認したいときだけ切り替え、終わったら止めてください。ポート番号は 1024 未満だと管理者権限が必要になるため、4443 のような番号を使っています。

ThreadingHTTPServer を使うと、ブラウザが複数の接続を同時に開いても一方が待たされません。

なお、Python 3.14 では http.server のコマンドライン起動に TLS 用の証明書と鍵を指定するオプションが追加されています(2026年10月現在)。お使いのバージョンで使えるかは python3 -m http.server --help で確認できます。古いバージョンの環境も考えるなら、上のようにスクリプトで書く方法が扱いやすいでしょう(ThreadingHTTPServer と directory 引数は Python 3.7 以降で使えます)。

動作を確認する

起動したら curl で確認します。自己署名証明書は信頼されていないので、作った証明書を「信頼する証明書」として渡します。

# 証明書を指定して接続(警告なしで 200 が返れば OK)
curl -v --cacert cert.pem https://localhost:4443/

# 証明書の中身や TLS のバージョンを確認
openssl s_client -connect localhost:4443 -servername localhost </dev/null

curl -k を使うと検証を飛ばせますが、何が問題か分からなくなるため、確認には --cacert を使うほうが原因の切り分けに役立ちます。

よくあるエラーと確認点

  • **FileNotFoundError や ssl.SSLError が起動時に出る**:証明書と鍵のパスが違うか、鍵と証明書の組み合わせが合っていません。スクリプトを別のディレクトリから起動すると相対パスがずれるため、ファイルの場所を確認します。
  • ブラウザで「接続はプライベートではありません」と出る:自己署名証明書では想定どおりの表示です。開発用に限ってブラウザ側で例外を許可するか、mkcert などでローカルの認証局を信頼させます。
  • **curl で SSL certificate problem: self-signed certificate のようなエラーが出る**(表記は curl の TLS ライブラリによって異なります):--cacert cert.pem を付けていないか、別の証明書を指しています。
  • ホスト名が一致しないというエラー:アクセスしている名前(localhost か IP か)が SAN に含まれているか確認します。
  • **http:// でアクセスすると応答が文字化けしたり接続が切れたりする**:HTTPS のポートに平文で接続しています。URL の https:// を確認します。
  • **Address already in use**:同じポートで別のプロセスが動いています。前に起動したサーバーが残っていないか確認します。

本番で HTTPS を提供する構成

外部に公開するアプリでは、Python のプロセスに直接 TLS を持たせるより、リバースプロキシで HTTPS を受けて、アプリには内部の HTTP で渡す構成が一般的です。

  1. アプリサーバー:Flask などの WSGI アプリは Gunicorn、FastAPI などの ASGI アプリは Uvicorn で 127.0.0.1:8000 のような内部ポートで動かす
  2. リバースプロキシ:Nginx や Caddy が 443 番で HTTPS を受け、アプリへ転送する
  3. 証明書:Let's Encrypt などの公的な認証局から発行し、certbot などで自動更新する

この形にすると、証明書の更新、HTTP から HTTPS へのリダイレクト、HTTP/2、静的ファイルの配信、アクセス制限などをプロキシ側でまとめて扱えます。アプリ側では、プロキシから渡される X-Forwarded-Proto などのヘッダーを信頼する設定(フレームワークごとに方法が異なります)をしておくと、リダイレクト先の URL が http:// になる問題を避けられます。

Gunicorn や Uvicorn にも証明書を直接読ませるオプションはありますが、証明書の自動更新後の再読み込みや、複数アプリの同居を考えると、プロキシで終端するほうが運用が楽です。

まとめ

  • http.server と ssl で立てる HTTPS サーバーは開発・テスト用で、本番には使わない
  • 証明書は SAN 付きで作り、秘密鍵の権限を絞って Git に含めない
  • ssl.wrap_socket() は Python 3.12 で削除済み。ssl.SSLContext の wrap_socket() を使う
  • 公開ディレクトリと待ち受けアドレスを限定し、確認は curl --cacert と openssl s_client で行う
  • 本番は Gunicorn / Uvicorn の前に Nginx などを置き、公的な証明書で TLS を終端する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲