SSD Nodes Learn
ガイド Matt Connor著者 Matt Connor ・更新日 2026-07-24

Ubuntu 24.04でCertbotをnginxにインストールする方法

Ubuntu 24.04でCertbotを導入する手順を解説します。aptとsnapの使い分けや、Let's Encryptの証明書発行に必須となるport 80の開放、HTTP-01チャレンジの仕組みについて詳しく説明します。二重インストールによる更新タイマーの競合を防ぐための注意点も記載しています。

Certbotのインストール: apt または snap

Ubuntu 24.04では、sudo apt install certbot python3-certbot-nginxを使用することで、公開された信頼できるLet's Encrypt証明書を発行できるCertbotが利用可能です。Certbotの公式ドキュメントではsnapの使用が推奨されています。両者の違いはわずかです。snapはアップストリームのリリースを追跡しますが、aptパッケージはLTSに同梱されたバージョンをベースとし、セキュリティ修正のみが適用されます。

一方方を選択してください。Certbotを2つインストールすると、同じ/etc/letsencryptツリーを対象とする更新タイマーが2つ作成されます。片方を忘れると、予期せぬ動作の原因となります。

aptを使用する場合:

sudo apt update
sudo apt install certbot python3-certbot-nginx

これにより、/usr/bin/certbot、nginx plugin、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/certbot

snapは独自のタイマーである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 の仕組みと port 80 が必須である理由

HTTP-01 チャレンジはコールバック方式です。example.com をカバーする証明書を Let's Encrypt に要求すると、Let's Encrypt はパブリック DNS で名前解決を行います。その後、解決されたアドレスの port 80 に接続し、http://example.com/.well-known/acme-challenge/<token> を要求します。サーバーは、Certbot がディスクに書き込んだものと全く同じトークン内容を返信する必要があります。これが仕組みの全容です。この仕組みにより、発行失敗の主な原因となる 3 つの制約が生じます。

  • port 80 はパブリックインターネットから到達可能である必要があります。ローカル環境からのみ接続できる状態では不十分です。ufw ルール、クラウドプロバイダーの security group、または VPS コンソールのファイアウォールによって 443 のみが許可されている場合、証明書の発行およびその後の更新はすべて失敗します。
  • DNS が既にこのサーバーを指している必要があります。検証サーバーは外部から独自に名前解決を行います。ローカルの /etc/hosts エントリやブラウザのキャッシュは影響しません。
  • AAAA レコードを公開している場合、IPv6 が優先されます。IPv6 での接続が完全に失敗した場合、Let's Encrypt は IPv4 で再試行します。しかし、接続はできるが別の内容を返す古い AAAA レコードが設定されている場合、検証は完全に失敗します。

リダイレクトは許可されています。検証プロセスは HTTP から HTTPS へのリダイレクトに従います。その際、転送先の証明書が欠落している、期限切れである、または自己署名であることは問題になりません。ただし、検証は port 80 以外からは開始できません。Certbot は TLS-ALPN-01 を実装していないため、「443 を使えばよい」という回避策は存在しません。

authenticatorの選択: --nginx, --webroot, --standalone

nginxが既に動作しており、ドメインの配信を行っている場合は、--nginx を使用してください。Certbotが設定ファイルを解析し、一時的なchallenge用の場所を挿入してnginxをreloadします。検証後、TLS設定をserver blockに書き込みます。ダウンタイムは発生しません。

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-interactive

nginxの設定ファイルを変更したくない場合は、--webroot が適しています。テンプレートから生成した設定や、gitで管理している設定、またはAnsibleで配布する設定を使用する場合に有効です。Certbotは、指定された公開ディレクトリにchallengeファイルのみを書き込みます。

sudo certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com \
  --deploy-hook "systemctl reload nginx"

port 80で何も待機していない場合は、--standalone を使用してください。例として、mail server、443のみを使用するAPI、またはnginxが起動する前に実行される初回起動スクリプトなどが挙げられます。Certbotが数秒間、自身でport 80をバインドします。nginxが動作中の場合はエラーになるため、実行前に停止させてください。

sudo certbot certonly --standalone -d mail.example.com \
  --pre-hook "systemctl stop nginx" \
  --post-hook "systemctl start nginx"

これらのhookは証明書の更新設定に記録されます。そのため、更新時には自動的にstop/startが実行されます。

証明書が存在する場合と存在しない場合の両方で動作する server block

鶏と卵の問題:ssl_certificate が存在しないファイルを指していると nginx は起動に失敗します。一方、nginx が停止していると Certbot は検証を行えません。まず port 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 location に ^~ プレフィックスを付けることが重要です。これにより、return 301 block が challenge リクエストを吸収するのを防ぎます。この location を port 80 に維持することで、サイト全体を HTTPS のみに変更した後も更新(renewal)が継続されます。

HTTP/2 の構文は nginx のバージョンに依存します。2つの形式を混在させると起動エラーが発生します。Ubuntu 24.04 の nginx 1.24 は inline 形式(listen 443 ssl http2;)を必要とします。Debian 13 の新しい nginx は、個別の http2 on; ディレクティブを必要とします。まず nginx -v を確認してください。

nginx の設定先は live/ にし、archive/ にしないでください。live/ の symlink は更新のたびに再設定されます。archive/ への絶対パスを使用すると、期限切れの証明書に固定されてしまいます。

WildcardsはDNS-01を意味し、DNS-01はプラグインを意味します

Wildcard certificate (*.example.com) は HTTP-01 では検証できません。取得すべき単一のhostnameが存在しないためです。DNS-01 が唯一の手段です。_acme-challenge.example.com TXT record を作成することで、ドメインの所有権を証明します。Certbot が自動でこの操作を行うには、DNSプロバイダーのAPI credentials が必要です。そのために各プロバイダー用のプラグインが存在します。Wildcard certificate の完全な手順 では、TXT record の仕組みと、manual mode で発生する更新の失敗について説明します。その後に Cloudflare 用の短い手順を記載します。

sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

sudo apt install python3-certbot-dns-cloudflare を使用する apt の場合は、手順が異なります。Credentials は root ユーザーのみが読み取れるファイルに保存します。

# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_here

Token の権限は、対象の zone に対する DNS-edit 権限のみに制限してください。これは DNS への鍵です。鍵と同様に厳重に管理してください。

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

Shell による glob 展開を防ぐため、Wildcard は引用符で囲んでください。DNS-01 は HTTP-01 では不可能なことも解決します。例えば、public port 80 を持たないホストの証明書取得です。これには、内部サービス、VPS上の self-hosted WireGuard VPN 経由でのみ到達可能なサーバー、プライベートインターフェース上の管理パネルなどが含まれます。

更新: 90日間、タイマー、および deploy hook

Let's Encrypt の証明書の有効期限は 90 日間です。Certbot は残り日数が 30 日未満になると更新処理を行います。これにより、更新に失敗しても、サービス停止に至る前に修正できる 30 日間の猶予が生まれます。Let's Encrypt は有効期限の警告メールを送信しなくなりました。通知は自動で行われないため、自身で監視を行う必要があります。

インストール時に設定されたタイマーを確認してください:

systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificates

certbot renew/etc/letsencrypt/renewal/ 内のすべての設定を確認し、30 日以内のものだけを対象にします。それ以外のものはスキップし、それ以外の対象は初回実行時と同じ flag を使用して更新します。そのため、初回の実行結果が重要になります。その設定が記録されるためです。

ディスク上のファイルを更新しただけでは、何も変わりません。nginx は再読み込み(reload)の指示を受けるまで、メモリ上の古い証明書を返し続けます。一度 deploy hook を設定してください:

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.sh

renewal-hooks/deploy/ 内の実行可能なファイルは、更新が成功した後に実行されます。--deploy-hook flag も単一の証明書に対して同様の動作を行い、renew_hook = ... を更新設定に保存します。certbot --nginx は自動で reload を行いますが、--webroot および --standalone の設定では行われません。hook が設定されていないと、certbot certificates が新しい証明書を報告しているにもかかわらず、サイトが期限切れの証明書を返し続ける原因になります。起動時に証明書を読み込む他のアプリケーションも、同様の hook を必要とします。例えば、Docker, TLS, バックアップを使用した Nextcloud VPS インストール のようなコンテナ化されたアプリには、ここで独自の再起動または reload ステップを設定する必要があります。

実際の更新テスト

sudo certbot renew --dry-run

Let's Encrypt の staging environment に対して full challenge を実行します。コードパス、firewall、DNS は本番と同じですが、rate-limit の制限はなく、ディスクへの書き込みも行われません。今日テストに合格すれば、サーバーの設定に変更がない限り、60日後の unattended renewal も成功します。

dry run では reload hook の動作は確認できません。Certbot のバージョンによって挙動が異なるためです。この部分は手動でテストしてください。hook スクリプトを直接実行し、systemctl reload nginx が成功すること、および sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf を確認してください。

実際に発生するエラー

Could not bind to IPv4 or IPv6. — nginxがすでにport 80を使用しているため、--standaloneが発生します。--nginxまたは--webrootを使用するか、実行前にnginxを停止してください。使用中のプロセスはsudo ss -lntp | grep ':80'で確認できます。

Timeout during connect (likely firewall problem) — Let's Encryptがport 80に到達できませんでした。以下の順序で確認してください:sudo ufw statussudo 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 — port 80には到達できますが、トークンが返されません。リクエストが別のserver blockに転送されています(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を使用してstaging環境でデバッグしてください。

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory — nginxの設定が、発行されていない証明書またはcertbot deleteで削除された証明書を指しています。TLS server blockをコメントアウトしてnginxを起動し、証明書を発行した後、設定を元に戻してください。

open() "/etc/letsencrypt/options-ssl-nginx.conf" failed — このファイルはnginxプラグインパッケージに含まれています。python3-certbot-nginxがインストールされていないcertonly環境では、プラグインを追加するか、includeの行を独自のssl_protocolsおよびssl_ciphersの設定に書き換えてください。

大規模環境での運用

1つの certificate には最大 100 個の名前を格納できます。そのため、1つの certbot --nginx -d a.example.com -d b.example.com ... に集約したくなります。しかし、古い DNS record が検証に失敗すると、その certificate 内の他のすべての名前も同時に無効になります。サイトごとに certificate を分ければ、個別にエラーが発生します。複数のサービスをホストするサーバーでは、この構成が最適です。サイト数が一定数を超えると、ACME 対応のフロントエンドが有効になります。例えば Docker Compose で複数のアプリを動かす Traefik reverse proxy は、自身で certificate のリクエストと更新を行います。この場合、Certbot は不要になります。

/etc/letsencrypt 全体をバックアップしてください。sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt の symlink も含めて保持します。このディレクトリには、再生成不可能な ACME account key である accounts/ が含まれています。新しい VPS への移行手順は以下の通りです。-a を使用してディレクトリを rsync し、Certbot をインストールし、DNS を設定し、切り替え前に certbot renew --dry-run を実行します。

サーバーの再構築や新しい LTS への移行を行うと、更新タイマーは引き継がれません。移行、スナップショットからの復元、または distro のアップグレード後は、必ず systemctl list-timers 'certbot*'--dry-run を実行してください。これを怠ると、自動更新されていると思っていた certificate が、89日後の午前3時に失効し、サイトがダウンします。

これらはすべて、パブリック IP を持ち、port 80 が外部に開放されている、管理下にあるマシン(つまり VPS)を前提としています。上記の仕組みは、どの VPS でも同様です。

同じ certificate の手順は nginx の代わりに Apache でも適用されます。また、パブリック certificate が使用できない場合は、Ubuntu 上の self-signed certificate で内部サービスをカバーできます。

FAQ

HTTPSのみを使用する場合でも、port 80を開放する必要がありますか?

はい、HTTP-01 challengeを使用する場合に必要です。Let's Encryptは常にport 80から検証リクエストを開始します。CertbotはTLS-ALPN-01を実装していないため、443のみを開放しているファイアウォールでは、初回発行とそれ以降の自動更新の両方がブロックされます。port 80からHTTPSへのリダイレクトは問題ありません。検証リクエストはリダイレクトに従います。port 80を完全に回避するには、プロバイダー用プラグインを使用したDNS-01のみが可能です。

Ubuntu 24.04上のnginxにインストールするCertbotは、aptとsnapのどちらが良いですか?

aptを使用してください。Ubuntu 24.04ではsudo apt install certbot python3-certbot-nginxにより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は証明書をメモリ内に保持しており、再読み込み(reload)が行われるまでディスク上の新しいファイルに気づかないためです。certbot --nginxは自動でreloadを行いますが、--webroot--standaloneでは行われません。そのため、更新自体は成功していても、ブラウザには期限切れの証明書が表示され続けることがあります。/etc/letsencrypt/renewal-hooks/deploy/nginx -t && systemctl reload nginxを実行する実行可能なスクリプトを配置すれば、更新成功のたびにreloadが実行されます。

certbot renew --dry-runを実行すれば、更新が成功すると判断できますか?

概ね判断できます。certbot renew --dry-runは、実際の検証と同じファイアウォール、DNS、コードパスを使用して、staging環境に対して本物のchallengeを実行します。レート制限の影響を受けず、ディスクにも何も書き込まれません。したがって、これが成功すればネットワーク部分は正常であると言えます。ただし、デプロイフックが確実に動作するかどうかまでは証明できません。フックのスクリプトを手動で実行し、sudo grep renew_hook /etc/letsencrypt/renewal/example.com.confを確認して別途テストしてください。