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

Ubuntu 24.04でChromeが認める自己署名証明書を作る方法

Ubuntu 24.04でOpenSSL 3.0.xを使用し、Chromeで警告が出ないSAN(Subject Alternative Name)付きの自己署名証明書を作成する手順を解説します。nginxやApacheへの設定に加え、curl -kを使わずにクライアントへ正しく証明書を信頼させる方法も紹介します。

作成するもの

モダンなブラウザやクライアントが受け入れる自己署名TLS証明書です。正しい subjectAltName、適切なキー権限、nginxまたはApacheへの組み込みを含みます。さらに、多くのガイドが省略している要素、つまり、警告を無視したりスクリプトに curl -k をハードコードしたりするのではなく、クライアントに正しく「信頼」させる方法についても解説します。最終的に、内部サービスが1つから6つに増えた際のために、5つのコマンドで構築できるプライベートCAを習得します。

まず、判断基準について説明します。自己署名証明書が適切なケースは、一般的に思われているよりもずっと少ないからです。もしサービスが、実際のDNS名を使用してパブリックインターネットからアクセス可能な場合は、このガイドを読むのを止めて、nginxでcertbotを使用してLet's Encryptの無料証明書を取得するか、Apacheでの同等の手順を行ってください。これらは無料で、自動更新され、世界中のすべてのブラウザがすでに信頼しています。パブリックサイトで自己署名証明書を使用すると、ユーザーにセキュリティ警告を無視する習慣を植え付けてしまいます。これは、単なるHTTPを使用するよりも悪い習慣です。

自己署名証明書が適切なのは、パブリックインターネットが介在しない場合です。例として、VPS上のWireGuardトンネルアドレスに紐付けられた管理パネル、プライベートネットワーク上のステージングサーバー、バックエンド間のサービス間通信、ホームラボのデバイス、または Webminがport 10000で自動生成するプレースホルダー証明書の代わりなどが挙げられます。いずれにせよ、Let's Encryptは 10.8.0.1git.internal.lan に対して証明書を発行できません。公開CAがプライベートIPや架空のTLDを証明書に含めることはありません。それらの名前については、あなたがCAとなります。

以下の手順はすべて、OpenSSL 3.0.x(確認には openssl version を使用)がインストールされた、新規のUbuntu 24.04環境で実行します。インターネット接続は不要です。オフライン環境でも動作します。

旧来のワンライナーで作成した証明書を Chrome が拒否する理由

2017年以前のチュートリアルでよく使われているコマンドは以下の通りです:

# Do not run this — shown so you recognise it in old guides
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout selfsigned.key -out selfsigned.crt

このコマンドは一連の対話型質問を行い、hostname を Common Name フィールドに設定します。しかし、生成される証明書には subjectAltName 拡張子が含まれません。そのため、その証明書は使用できません。Chrome は 2017 年 4 月の version 58 から Common Name を読み取らなくなりました。RFC 2818 では、すでに 2000 年に CN による照合は非推奨となっています。Firefox、Safari、curl、Python も同様の挙動を示します。現在の証明書は SAN 拡張によってサーバーを識別します。SAN がない場合、ブラウザには次のように表示されます:

NET::ERR_CERT_COMMON_NAME_INVALID

This server could not prove that it is git.internal.lan; its security
certificate does not specify Subject Alternative Names.

このエラーは、trust-store を操作しても解決しません。証明書に識別情報が含まれていないためです。もし現在 NET::ERR_CERT_COMMON_NAME_INVALID が表示されているなら、証明書に SAN が設定されていないか、誤った設定になっています。新しい証明書を作成する必要があります。幸い、解決策はコマンド一つです。

ブラウザが受け入れる証明書の発行: コマンド1つで完了

OpenSSL 1.1.1 で -addext フラグが追加されました。これにより、SAN を注入するために古いガイドで行われていた複雑な config-file の操作は不要になりました。Ubuntu 24.04 での実行例は以下の通りです:

sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 730 -noenc \
  -keyout /etc/ssl/private/git.internal.key \
  -out /etc/ssl/certs/git.internal.crt \
  -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"

各フラグの役割:

  • -x509 は、署名要求(CSR)の代わりに自己署名証明書を直接出力します。
  • -newkey rsa:4096 は、同じステップで新しい鍵を生成します。RSA 4096 は古いクライアントでも問題なく動作します。接続するすべてのクライアントが最新であれば、-newkey ec -pkeyopt ec_paramgen_curve:P-256 の方がサイズが小さく高速です。
  • -noenc は、従来の -nodes の OpenSSL 3.x での表記です。鍵にパスフレーズを設定しません。どちらの表記も有効です。パスフレーズ付きの鍵を使用すると、起動のたびに nginx が入力を待機して停止するため、サーバー用の鍵にはこの設定が必要です。
  • -days 730 — 2年間。詳細については有効期限のセクションを参照してください。
  • -subj は、対話的な質問に自動回答します。CN は現在は見た目上のものですが、念のためプライマリ名に設定してください。一部のツールで表示されます。
  • -addext "subjectAltName=..." は、最も重要なフラグです。クライアントが入力する すべての 名前と すべての IP アドレスをリストしてください: ホスト名には DNS: (DNS:*.internal.lan のようなワイルドカードも可)、アドレスには IP: を使用します。もし https://10.8.0.1 にアクセスするクライアントがある場合、IP:10.8.0.1 のエントリが必要です。DNS のみで SAN を設定すると、NET::ERR_CERT_COMMON_NAME_INVALID が再発します。

設定を適用する前に、SAN が正しく書き込まれているか確認してください:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltName

正しい出力:

X509v3 Subject Alternative Name:
    DNS:git.internal.lan, IP Address:10.8.0.1

もし No extensions in certificate と表示される場合は、証明書に SAN が含まれていません。ブラウザに拒否されるため、そのまま進めず再生成してください。

キーの権限を制限する

システム上の全ユーザーが読み取れるプライベートキーは、もはやプライベートキーではありません。Ubuntuでは、/etc/ssl/privateは既に710 root:ssl-certに設定されており、不用意な閲覧を防いでいますが、ファイル自体の権限を明示的に設定してください。

sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.key

nginxやApacheは、権限を降格させる前にrootとして証明書を読み込むため、root:rootモード 600で動作します。もしキーが、独自のユーザーで実行されるサービス(Nodeアプリ、Gitea、Pythonデーモンなど)で使用され、そのサービス自体がキーを読み込む場合は、chownをそのサービスユーザーに変更してください。その際もモードは600にします。絶対にやってはいけないことは、モード 644にすること、git repositoryにコピーすること、または/tmpにコピーすることです。

nginxへの組み込み

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name git.internal.lan;

    ssl_certificate     /etc/ssl/certs/git.internal.crt;
    ssl_certificate_key /etc/ssl/private/git.internal.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
sudo nginx -t && sudo systemctl reload nginx

nginx -tは、reloadを実行する前にsyntax is oktest is successfulを出力する必要があります。もしSSL_CTX_use_PrivateKey_file() failed ... key values mismatchが出力される場合は、証明書とkeyが異なる生成プロセスで作成されています。詳細はfailure-modesのセクションを参照してください。

Apacheへの組み込み

sudo a2enmod ssl proxy proxy_http

ssl だけでは不十分です。以下の vhost は ProxyPass を使用しています。mod_proxymod_proxy_http がないと、config test は Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration で失敗します。vhost を /etc/apache2/sites-available/git-internal.conf として保存してください。

<VirtualHost *:443>
    ServerName git.internal.lan
    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/git.internal.crt
    SSLCertificateKeyFile   /etc/ssl/private/git.internal.key

    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
sudo a2ensite git-internal
sudo apache2ctl configtest && sudo systemctl reload apache2

configtestSyntax OK に回答するはずです。次に、クライアントマシンからテストを行います。

curl -v https://git.internal.lan/

すると、以下のエラーが表示されます。

curl: (60) SSL certificate problem: self-signed certificate

これはバグではありません。TLS が正常に動作している証拠です。curl は指定された certificate を認識できないため、認証できないサーバーとの通信を拒否します。次のセクションで、正しい修正方法を説明します。これは、現在インターネット上の多くのサイトが行っている方法とは異なります。

クライアントに信頼させる方法 — 避けるべきアンチパターン

まず、誤った修正方法を挙げます。これらは証明書を信頼させるものではありません。スクリプトへの curl -k (または --insecure) の埋め込み、Python requests での verify=False、Node での NODE_TLS_REJECT_UNAUTHORIZED=0 などです。これらは証明書検証を off にします。その結果、クライアントは攻撃者が用意した証明書を含む、あらゆる 証明書を提示するサーバーとの通信を許可してしまいます。TLS のオーバーヘッドは残りますが、本来の目的である認証機能が失われます。さらに、これらのフラグは蔓延します。ある cron ジョブに貼り付けられ、次にデプロイ用スクリプト、そして本番コードへと広がり、どの接続が一時的なものだったのか誰も分からなくなります。デバッグ作業が終わった後も verify=False が残っている場合、設計に問題があります。

正しい修正方法は、各クライアント OS にこの証明書が信頼できるルートであることを教えることです。Ubuntu および Debian クライアントの場合:

sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificates

出力の中で重要な行(この後に Running hooks in /etc/ca-certificates/update.d... ブロックが続きます)は以下の通りです:

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

これらには2つの注意点があります。ファイル名は必ず .crt で終わる必要があります。.pem 拡張子は無視され、エラーメッセージなしで 0 added が発生します。また、内容は PEM 形式である必要があります。ファイルは -----BEGIN CERTIFICATE----- で始まります。DER バイナリの場合は、まず openssl x509 -inform der -in file.der -out file.crt で変換してください。自己署名証明書自体をルートとして追加する方法が有効なのは、自己署名証明書がそれ自身をルートとして定義しているからです。

これを完了すれば、curlwgetgitapt、およびシステムバンドルに対して OpenSSL を使用するすべてのツールは、フラグなしでサーバーを信頼します。一部のクライアントは独自の信頼ストアを持っているため、個別の対応が必要です:

  • Linux 上の Chrome/Chromium はシステムストアではなく NSS データベースを読み込みます:sudo apt install libnss3-tools、その後ユーザーごとに certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt
  • Firefox は独自のストアを持っています:Settings → Privacy & Security → Certificates → Import、または about:configsecurity.enterprise_roots.enabledtrue に変更してシステムストアを読み込ませます。
  • Python requests は独自の CA バンドル (certifi) を同梱しており、システムストアを無視します:verify="/usr/local/share/ca-certificates/git.internal.crt" を渡すか、REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt を export してください。
  • Node.js: NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt を export してください。

Windows クライアントの場合は、.crt をダブルクリックし、Trusted Root Certification Authorities にインストールしてください。macOS の場合は、Keychain Access で System キーチェーンに追加し、Always Trust に設定してください。

多数のサービスを支える一つのルート:小規模なプライベートCA

証明書ごとに信頼設定を行う方法は、拡張性に欠けます。例えば、6つのサービスと4台のクライアントがある場合、24回のインストール作業が必要です。サービスが増えるたびに、この作業は増え続けます。解決策はプライベートCAの導入です。クライアントには一つのルート証明書のみを信頼させ、各サービスの証明書をそのルートで署名します。

手軽な選択肢は mkcert です。これは Ubuntu 24.04 のリポジトリに含まれており、update-ca-certificates では対応できない NSS ストア (Chrome, Firefox) を処理できます。

sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1

mkcert -install はルート証明書を作成し、そのマシンのすべての信頼ストアに登録します。3番目のコマンドは git.internal.lan+2.pemgit.internal.lan+2-key.pem を出力します。これらは、前述の nginx や Apache の設定にそのまま使用できます。このツールは開発環境を想定しています。ルートキーは -install を実行したマシンに保存されるため、開発用ノートPCには最適ですが、サーバー群の管理には適していません。

サーバー用途では、OpenSSL を使用して5つのコマンドでCAを構築できます。

openssl genrsa -out lab-ca.key 4096
openssl req -x509 -new -key lab-ca.key -sha256 -days 3650 \
  -out lab-ca.crt -subj "/CN=Lab Internal CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out git.key 2048
openssl req -new -key git.key -out git.csr -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"
openssl x509 -req -in git.csr -CA lab-ca.crt -CAkey lab-ca.key \
  -CAcreateserial -days 730 -sha256 -copy_extensions copy -out git.crt

注意点は最後のコマンドです。openssl x509 -req は、デフォルトで CSR からすべての拡張情報を削除します。これには、慎重に追加した SAN も含まれます。-copy_extensions copy (OpenSSL 3.x のオプションであり、24.04 で動作します) を使用すると、拡張情報を保持できます。これを使用しないと、署名済み証明書に SAN が含まれず、Chrome で再び NET::ERR_CERT_COMMON_NAME_INVALID が表示されます。確認には、前回と同様の openssl x509 -noout -ext subjectAltName を使用してください。

lab-ca.crt を、前述の信頼ストアの手順に従ってクライアントに配布してください。一度のマシンにつき、一度の実行で済みます。lab-ca.key は極めて重要な資産として保護してください。権限は mode 600 に設定し、理想的には署名対象のサーバーとは別のマシンで管理してください。なぜなら、このキーを保持する者は、クライアントが信頼するあらゆるドメインに対して証明書を偽造できるからです。

有効期限とローテーション

パブリックCAの証明書寿命は短縮されています。CA/Browser Forumは、2026年3月に新規発行されるパブリック信頼証明書の期限を398日から200日に制限しました。さらに2027年には100日、2029年3月までには47日へと段階的に短縮される予定です。ただし、これらのルールはパブリック信頼CAに適用されるものです。プライベートCAはこれらのルールの対象外であり、ブラウザも手動でインストールされたルート証明書に対してこれらを強制しません。実務上の制限は1つあります。Appleのプラットフォームは、発行元に関わらず、有効期間が825日を超えるTLSサーバー証明書を拒否します。iPhoneやMacとの接続が必要な場合は、リーフ証明書の期間を2年以内に設定してください。-days 730 を使用すれば、すべての環境でこの基準をクリアできます。10年間のルート証明書と2年間のリーフ証明書の組み合わせは、内部運用において適切な構成です。

有効期限の長い証明書が失敗するパターンはただ一つです。誰も設定日を覚えていない日付に、すべてが同時に、静かに失効します。現在の状況を確認してください:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddate

更新作業は実際のカレンダーに登録するか、30日前に cron で通知するようにしてください。openssl x509 -checkend 2592000 -in cert.crt は、有効期限まで残り指定秒数になると、非ゼロのステータスで終了します。すでに Uptime Kuma をステータス監視に使用している場合、そのHTTPSモニター機能を使えば、証明書の期限切れ間近を無料で検知できます。

プライベートCAによるローテーションは非常に単純です。CSRの作成と署名コマンドを再実行し、ファイルを入れ替え、Webサーバーをリロードするだけです。ルート証明書は変更されないため、クライアント側に影響はありません。

失敗パターンと表示される文字列

NET::ERR_CERT_AUTHORITY_INVALID — 証明書に問題があるのではなく、信頼設定を行う前の正常な状態です。ルート証明書をインストールしたもこの状態が続く場合は、以下の原因が考えられます。Linuxの場合、ChromeはシステムストアではなくNSSを参照しています(certutilの手順を参照してください)。または、コピーしたファイルが.crtで終わっておらず、update-ca-certificates0 addedと表示されました。あるいは、サーバーが信頼した証明書とは別の証明書を提示しています。openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256を使用してfingerprintを比較してください。

NET::ERR_CERT_COMMON_NAME_INVALID — 証明書にSANがないか、SANがアドレスバーの名前をカバーしていません。典型的な例は、SANにはDNS:git.internal.lanが記載されているが、ユーザーがhttps://10.8.0.1にアクセスしているケースです。信頼ストアの変更では解決できません。不足しているエントリを含めて証明書を再発行してください。

curl: (60) SSL certificate problem: self-signed certificate — curlが証明書を信頼していません。プライベートCAで署名された証明書の場合、self-signed certificate in certificate chainも同様の意味です。一時的な解決策はcurl --cacert lab-ca.crt https://...です。恒久的な解決策は信頼ストアへの追加です。-kではありません。

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (または Expecting: CERTIFICATE REQUESTno start line) — PEM形式の混同です。OpenSSLに誤ったファイル形式を渡しています。証明書が必要な場所にkeyやCSRを渡したか、PEMが必要な場所にDERバイナリを渡しています。head -1 filenameで実際の形式を確認してください。証明書は-----BEGIN CERTIFICATE-----で始まります。DER形式の場合は、openssl x509 -inform der -in file.der -out file.crtで変換してください。

nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch) — 証明書とkeyのペアが一致していません。通常、生成コマンドを2回実行したためにファイルが混在したことが原因です。openssl x509 -in git.internal.crt -noout -pubkey | sha256sumopenssl pkey -in git.internal.key -pubout | sha256sumを比較して確認してください。ハッシュ値が一致すれば、それらはペアです。一致しない場合は、両方を同時に再生成してください。

FAQ

自己署名証明書を作成したのに、Chromeで「保護されていない通信」と表示されるのはなぜですか?

エラーが NET::ERR_CERT_AUTHORITY_INVALID の場合、証明書自体は正常です。Chromeがまだその証明書を信頼していないだけです。クライアントの信頼ストアに証明書(またはプライベートCAのルート証明書)をインストールしてください。Linuxの場合、Chromeはシステムストアではなく certutil を介したNSSデータベースを使用することに注意してください。エラーが NET::ERR_CERT_COMMON_NAME_INVALID の場合は、証明書にURLと一致するSubject Alternative Nameが含まれていません。-addext "subjectAltName=..." を使用して証明書を再発行する必要があります。

-k を使わずに、curl で自己署名証明書を信頼させるにはどうすればよいですか?

証明書(PEM形式、.crt 拡張子)を /usr/local/share/ca-certificates/ にコピーし、sudo update-ca-certificates を実行してください。出力に 1 added と表示される必要があります。その後、curl は公開証明書と同様にその証明書を検証します。システムを変更せずに一度だけリクエストを行う場合は、curl --cacert /path/to/cert.crt を使用してそのファイルのみを検証できます。-k は検証を完全に無効化しますが、スクリプトでの使用は推奨されません。

自己署名証明書の有効期間はどのくらいに設定できますか?

技術的には、任意の期間に設定可能です。CA/Browser Forum の制限(現在は200日、2029年までに47日)は、公開CAに適用されるものであり、プライベートな信頼関係には適用されません。実務上は、サーバー証明書の期限を825日以内に設定してください。発行元に関わらず、Apple製デバイスはそれ以上の期間の証明書を拒否するためです。10年間のプライベートルートと、2年間の(-days 730)リーフ証明書の組み合わせが妥当なデフォルト設定です。期限切れの内部証明書は、誰も覚えていないタイミングでシステム全体を停止させるため、更新スケジュールを必ず管理してください。

自己署名証明書と Let's Encrypt のどちらを使うべきですか?

サービスが公開DNS名を持ち、インターネットからアクセス可能な場合は、常に Let's Encrypt を使用してください。無料かつ自動化されており、すべてのクライアントから既に信頼されています。自己署名(またはプライベートCA)は、Let's Encrypt が発行できないもの、つまりプライベートIP、.lan のような内部専用のホスト名、エアギャップ環境のネットワーク、およびVPNの背後に意図的に隠蔽されたサービスに対して使用します。選択基準は到達可能性と命名規則であり、暗号強度ではありません。暗号技術自体は同一です。

/usr/local/share/ca-certificates に追加しても証明書が拒否されるのはなぜですか?

以下の3点を確認してください。まず、ファイル拡張子が .crt である必要があります。.pem 拡張子の場合は無視され、update-ca-certificates0 added を報告します。次に、内容は DER バイナリではなく、-----BEGIN CERTIFICATE----- で始まる PEM テキストである必要があります。最後に、アプリケーションが実際にシステムストアを使用しているか確認してください。Linux上の Chrome、Firefox、Python requests、Node.js、Java は、それぞれ独自の信頼ストアを保持しているため、個別に証明書を追加する必要があります。

#openssl#tls#self-signed#ubuntu#security