Ubuntu 24.04でCertbotをインストールしてNginxをHTTPS化する方法
Ubuntu 24.04でCertbotをインストールする手順を解説します。aptとsnapのどちらを選ぶべきか、またLet's Encryptの証明書発行で失敗しやすい80番ポートの接続制限や更新時の注意点を詳しく説明します。
Certbotのインストール: aptまたはsnap
Ubuntu 24.04では、sudo apt install certbot python3-certbot-nginxを使用することで、公的に信頼されたLet's Encryptの証明書を発行できるCertbotが利用可能です。Certbotの公式ドキュメントではsnapの使用が推奨されています。両者の違いはわずかであり、snapはアップストリームのリリースを追跡し、アーカイブパッケージはLTSで提供されたバージョンを維持しつつセキュリティ修正が適用されます。
どちらか一方を選択してください。Certbotを2つインストールすると、同じ/etc/letsencryptツリーに対して更新タイマーが2つ設定されることになり、忘れていた方のタイマーが予期せぬタイミングで動作する原因となります。
aptを使用する場合:
sudo apt update
sudo apt install certbot python3-certbot-nginxこれにより、/usr/bin/certbot、nginxプラグイン、certbot.serviceとcertbot.timerのペア、およびsystemd下で何もしない/etc/cron.d/certbotエントリがインストールされます。
snapを使用する場合:
sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotsnapには独自のタイマーであるsnap.certbot.renew.timerが含まれています。snapをインストールする前に、aptパッケージを削除してください。
インストール後の動作はどちらも同じです。Certbot 2.xはデフォルトでECDSA (P-256) 鍵を使用します。ECDSAに対応していないクライアントを使用する場合のみ、--key-type rsaを指定してください。すべての状態は/etc/letsencrypt以下に保存されます。archive/には実際の鍵と証明書ファイルが、live/には現在のファイルへのシンボリックリンクが、renewal/には証明書ごとの設定ファイルが、accounts/にはACMEアカウントの鍵がそれぞれ格納されます。
HTTP-01 の仕組みと 80 番ポートが必須である理由
HTTP-01 チャレンジはコールバックの一種です。Let's Encrypt に対して example.com の証明書発行を要求すると、Let's Encrypt は公開 DNS で名前解決を行い、見つかったアドレスの 80 番ポート に接続して http://example.com/.well-known/acme-challenge/<token> を要求します。サーバー側は、Certbot がディスクに書き込んだトークンの中身をそのまま応答します。これが仕組みのすべてです。この仕組みから 3 つの帰結が生じ、これらが発行失敗の主な原因となります。
- 80 番ポートはパブリックインターネットから到達可能である必要があります。 ローカル環境からアクセスできるだけでは不十分です。
ufwルール、クラウドプロバイダーのセキュリティグループ、あるいは VPS コンソールのファイアウォール設定で 443 番ポートのみを開放している場合、初回発行だけでなく将来の更新もすべて失敗します。 - DNS はすでにこのサーバーを指している必要があります。 検証サーバーは外部から独自に名前解決を行います。あなたの
/etc/hostsエントリやブラウザのキャッシュは、検証サーバーには何の影響も与えません。 - AAAA レコードを公開している場合、IPv6 が優先的に試行されます。 IPv6 接続が完全に失敗した場合は IPv4 で再試行されますが、古い AAAA レコードが別のホストを指しており、そこで接続が確立されて別のコンテンツが返されると、即座にエラーとなります。
リダイレクトは許可されています。検証プロセスは HTTP から HTTPS へのリダイレクトを追跡し、その先の証明書が欠落していたり、期限切れであったり、自己署名であっても問題にしません。ただし、80 番ポート以外から開始することはできません。Certbot には TLS-ALPN-01 の実装がないため、「443 番ポートを使えばよい」という回避策は存在しません。
認証方式の選択: --nginx, --webroot, --standalone
nginx が既に稼働しており、対象ドメインを配信している場合は --nginx が標準的な選択肢です。Certbot が設定ファイルを解析し、一時的なチャレンジ用ロケーションを挿入して nginx をリロードし、検証後に TLS ディレクティブをサーバーブロックへ書き込みます。ダウンタイムは発生しません。
sudo certbot --nginx -d example.com -d www.example.com新規サーバー構築時のスクリプト例:
sudo certbot --nginx \
-d example.com -d www.example.com \
--agree-tos -m ops@example.com --no-eff-email \
--redirect --non-interactiveCertbot に nginx の設定ファイルを操作させたくない場合は --webroot が適しています。テンプレートから生成した設定を git で管理している場合や、Ansible で配布している場合に有効です。Certbot はチャレンジ用ファイルのみを、既に配信中のディレクトリへ書き込みます。
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"ポート 80 で何も待ち受けていない場合(メールサーバー、443 番のみを扱う API、nginx インストール前の初回起動スクリプトなど)は --standalone が適しています。Certbot が数秒間だけ自身でポート 80 をバインドします。nginx が稼働していると失敗するため、実行前後で停止させる必要があります:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"これらのフックは証明書の更新設定に記録されるため、更新時にも同様の停止・起動処理が自動的に行われます。
証明書の有無にかかわらず動作するサーバーブロック
鶏と卵の問題があります。nginx は存在しないファイルを指す ssl_certificate があると起動を拒否し、Certbot は nginx が停止していると検証できません。まずは 80 番ポートでサイトを立ち上げてください。
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}sudo nginx -t && sudo systemctl reload nginx を実行し、サーバーの外部から curl -I http://example.com/ が応答することを確認してから、証明書を発行します。その後は以下の通りです。
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}ACME の場所に対する ^~ プレフィックスは重要な役割を果たします。これは、return 301 ブロックがチャレンジリクエストを横取りするのを防ぎます。この場所を 80 番ポートに残しておくことで、サイトの残りの部分が HTTPS 専用になった後も更新処理が継続されます。
上記のブロックはどちらもディスク上のファイルを配信します。nginx がアプリケーションのフロントエンドとして機能している場合、location / は proxy_pass ブロックに置き換わります。リバースプロキシサーバーブロックの各行の解説では、アプリが必要とするヘッダーについて説明しています。ACME の場所と TLS ディレクティブはそのまま維持してください。
HTTP/2 の構文は nginx のバージョンに依存し、両方の形式を混在させると起動エラーが発生します。Ubuntu 24.04 に同梱されている nginx 1.24 では、listen 443 ssl http2; のようにインラインで記述する必要があります。Debian 13 に同梱されている新しい nginx では、個別の http2 on; ディレクティブが必要です。最初に nginx -v を確認してください。
nginx には live/ を指定し、archive/ を直接指定してはいけません。live/ のシンボリックリンクは更新のたびに付け替えられます。archive/ 内の固定パスを指定すると、期限切れの証明書に固定されてしまいます。
ワイルドカード証明書には DNS-01 が必須であり、DNS-01 にはプラグインが必要です
ワイルドカード証明書(*.example.com)は HTTP-01 では検証できません。ファイルを取得するための単一のホスト名が存在しないためです。DNS-01 が唯一の手段であり、_acme-challenge.example.com TXT レコードを公開することでドメインの管理権限を証明します。Certbot がこれを自動で行うには DNS プロバイダーの API 認証情報が必要であり、そのためにプロバイダープラグインが存在します。ワイルドカード証明書の完全ガイドでは、TXT レコードの仕組みと手動モードにおける更新の落とし穴を解説しています。以下は Cloudflare を使用した簡潔な手順です。
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflareapt を使用する場合、パスは sudo apt install python3-certbot-dns-cloudflare になります。認証情報は root ユーザーのみが読み取れるファイルに保存します。
# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_hereトークンの権限は、対象ゾーンに対する DNS 編集権限のみに限定してください。これは DNS の鍵となるため、慎重に扱ってください。
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'シェルによるワイルドカードの展開を防ぐため、ワイルドカード部分は引用符で囲んでください。DNS-01 は HTTP-01 では不可能なケースも解決します。例えば、公開ポート 80 を持たないホスト、内部サービス、VPS 上のセルフホスト WireGuard VPN 経由でのみ到達可能なボックス、プライベートインターフェース上の管理パネルなどが対象です。
更新: 90日間の有効期限、タイマー、デプロイフック
Let's Encryptの証明書は90日間有効です。Certbotは有効期限まで30日を切ると更新を実行します。これにより、更新に失敗しても30日間の猶予があるため、サービス停止ではなく修正可能な問題として対処できます。Let's Encryptは期限切れの警告メールを送信しなくなったため、通知は期待できません。監視は自身で行う必要があります。
インストール時に設定されたタイマーを確認してください。
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renewは/etc/letsencrypt/renewal/内のすべての設定を走査し、有効期限まで30日以上あるものはスキップし、残りを初回実行時と全く同じフラグを使用して更新します。そのため、初回実行時の設定が重要になります。その設定が記録として残るからです。
ディスク上のファイルを更新しても、それだけでは何も変わりません。nginxは再読み込みの指示があるまで、メモリ上の古い証明書を提供し続けます。一度だけデプロイフックを設定してください。
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shrenewal-hooks/deploy/内の実行可能なファイルは、更新が成功するたびに実行されます。--deploy-hookフラグは単一の証明書に対して同様の役割を果たし、renew_hook = ...を更新設定に保存します。certbot --nginxは自動的に再読み込みを行いますが、--webrootや--standaloneの構成では自動で行われません。フックの設定漏れは、certbot certificatesが正常に更新されたと報告しているにもかかわらず、サイトが期限切れの証明書を提供し続ける原因となります。起動時に証明書を読み込む他のサービスも同様のフックを必要とします。Docker、TLS、バックアップを用いたNextcloud VPS構築のようなコンテナ化されたアプリも、ここで再起動や再読み込みのステップを設定する必要があります。
更新テストの実施
sudo certbot renew --dry-runこのコマンドは、Let's Encrypt のステージング環境に対してフルチャレンジを実行します。本番と同じコードパス、ファイアウォール設定、DNS を使用し、レート制限の消費やディスクへの書き込みは発生しません。今日このテストが成功すれば、サーバー環境に変更がない限り、60 日後の自動更新も成功します。
ドライランでは、リロードフックが正常に動作するかまでは検証できません。この挙動は Certbot のバージョンによって異なるためです。フックの動作は手動でテストしてください。フックスクリプトを直接実行し、systemctl reload nginx が成功することを確認した上で、sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf を実行して結果をチェックします。
実際に遭遇するエラー
Could not bind to IPv4 or IPv6.、--standalone: nginx がすでに 80 番ポートを占有しています。--nginx または --webroot を使用するか、実行時に nginx を停止してください。sudo ss -lntp | grep ':80' で占有しているプロセスを確認します。
Timeout during connect (likely firewall problem): Let's Encrypt が 80 番ポートに到達できません。外側から順に確認します。sudo ufw status (sudo ufw allow 'Nginx Full' で開放)、次に VPS プロバイダーのファイアウォール、最後に DNS です。サーバー外の環境から curl -sSv http://example.com/.well-known/acme-challenge/test でテストしてください。古い AAAA レコードも同じメッセージの原因となります。
unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404: 80 番ポートには到達可能ですが、トークンが提供されていません。リクエストが別のサーバーブロックに到達しています(default_server を所有しているブロックを確認してください)。または、-w に渡されたディレクトリが nginx の公開ディレクトリと一致していません。/var/www/example.com/.well-known/acme-challenge/test にファイルを配置して外部から取得を試みてください。そこで 404 が返る場合、証明書の問題ではありません。
DNS problem: NXDOMAIN looking up A for example.com: ドメイン名がパブリックに解決されません。レコードの反映待ち、またはレジストラが管理していないゾーンのレコードである可能性があります。
too many certificates already issued for: example.com: レート制限です。デバッグを繰り返すと発生します。Let's Encrypt は同一のドメイン名セットに対する証明書発行を週 5 回までに制限しています。また、登録ドメインごとに週 50 回の新規発行制限があります。これらは時間の経過以外に解除する方法はありません。--dry-run を使用してステージング環境でデバッグしてください。
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory: nginx が発行されていない証明書、または certbot delete で削除された証明書を参照しています。TLS サーバーブロックをコメントアウトして nginx を起動し、証明書を発行してからブロックを元に戻してください。
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed: そのファイルは nginx プラグインパッケージに含まれています。certonly 環境で python3-certbot-nginx がない場合、プラグインを追加するか、include 行を独自の ssl_protocols および ssl_ciphers 設定に置き換えてください。
大規模環境での運用
1枚の証明書に最大100個のドメイン名を含めることは可能であり、1つの certbot --nginx -d a.example.com -d b.example.com ... で管理するのは魅力的です。しかし、古いDNSレコードが1つでも検証に失敗すると、その証明書に含まれるすべてのドメイン名が無効になります。サイトごとに証明書を分ければ個別に障害が切り分けられるため、複数のサイトをホストするサーバーではこちらが推奨されます。サイト数が増えた場合、ACMEに対応したリバースプロキシを導入する価値があります。Docker Composeで複数のアプリを動かすTraefikリバースプロキシ を使用すれば、証明書の取得と更新を自動化でき、Certbotを個別に管理する必要がなくなります。どのプロキシを採用するかは用途次第ですが、Nginx、Caddy、Traefikの比較 は、証明書管理やアプリごとの設定をどれだけプロキシ側に任せたいかによって決まります。
/etc/letsencrypt 全体、特に sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt をシンボリックリンクを維持したままバックアップしてください。このディレクトリには accounts/ が含まれており、ACMEアカウントキーは再生成しても同一にはなりません。新しいVPSへの移行は、-a を使用してディレクトリを同期し、Certbotをインストールし、DNSを切り替え、切り替え前に certbot renew --dry-run を実行するだけで完了します。
サーバーを再構築したり新しいLTSへ移行したりすると、更新タイマーは引き継がれません。移行、スナップショットからの復元、ディストリビューションのアップグレード後は、必ず systemctl list-timers 'certbot*' を実行し、--dry-run を1回行ってください。これを怠ると、89日後の午前3時に証明書の有効期限が切れ、サイトが停止する原因となります。
これらはすべて、公開IPを持ち、80番ポートが外部に開放されているサーバー(VPSなど)を自身で管理していることを前提としています。上記の手順は、どの環境でも共通です。
同様の証明書の手順は NginxではなくApacheを使用する場合 にも適用されます。また、公開証明書が利用できない環境では、Ubuntuでの自己署名証明書 を使用して内部サービスを保護します。
FAQ
HTTPS だけでサイトを公開する場合でも 80 番ポートを開放する必要がありますか?
はい、HTTP-01 チャレンジのために必要です。Let's Encrypt は常に 80 番ポートで検証リクエストを開始します。Certbot には TLS-ALPN-01 の実装がないため、443 番ポートのみを開放するファイアウォールでは、初回発行時およびその後のすべての自動更新が失敗します。80 番ポートから HTTPS へのリダイレクトは問題ありません。検証はリダイレクトに従います。80 番ポートを完全に回避する唯一の方法は、プロバイダープラグインを使用した DNS-01 チャレンジです。
Ubuntu 24.04 の Nginx で Certbot を使う場合、apt と snap のどちらをインストールすべきですか?
apt を使用してください。sudo apt install certbot python3-certbot-nginx は Ubuntu 24.04 で Certbot 2.9.0 を提供します。これは本ガイドのすべての手順に十分対応しており、unattended-upgrades を通じてセキュリティパッチが適用され、snapd を必要としません。最新のリリースが直ちに必要であるか、snap としてのみ配布されている DNS プラグインが必要な場合にのみ snap を選択してください。いずれにせよ、どちらか一方のみを選択してください。2 つインストールすると、同じ /etc/letsencrypt ツリーを指す更新タイマーが 2 つ存在することになり、管理を忘れた方が更新失敗の原因となります。
Certbot で Nginx 用のワイルドカード証明書を発行できますか?
DNS-01 を使用する場合のみ可能です。*.example.com のようなワイルドカードには、チャレンジファイルを取得するための単一のホスト名が存在しないため、--nginx、--webroot、--standalone はすべて使用できません。DNS プロバイダー用のプラグインをインストールし、root 権限のみで読み取り可能な認証情報ファイルにスコープを限定した API トークンを保存し、certbot certonly --dns-cloudflare -d example.com -d '*.example.com' を実行してください。その際、シェルによるワイルドカードの展開を防ぐために引用符で囲んでください。
更新に成功したのに Nginx が古い証明書を提示するのはなぜですか?
Nginx は証明書をメモリ上に保持しており、リロードされるまでディスク上の新しいファイルを認識しません。certbot --nginx は自動的にリロードを行いますが、--webroot や --standalone の実行ではリロードされません。そのため、更新自体は成功しても、ブラウザ上では期限切れの証明書が表示され続けることがあります。/etc/letsencrypt/renewal-hooks/deploy/ に nginx -t && systemctl reload nginx を実行する実行可能なスクリプトを配置すれば、更新成功のたびに自動的にリロードが実行されます。
certbot renew --dry-run は更新が成功することを保証しますか?
概ね保証されます。これはステージング環境に対して実際のチャレンジを実行するもので、ファイアウォール、DNS、コードパスは本番と同じです。レート制限の消費はなく、ディスクへの書き込みも行われないため、成功すればネットワーク周りの設定は健全であると判断できます。ただし、デプロイフックが確実に動作するかまでは証明できません。フックスクリプトを手動で実行し、sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf を確認して個別にテストしてください。