1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ローカル開発環境で HTTPS を実現する mkcert + nginx 構成

1
Last updated at Posted at 2026-05-03

ローカル開発でも HTTPS が必要になる場面があります。Service Worker、Secure Cookie、HTTP/2 の動作確認など、HTTP では再現できない挙動があります。

この記事では mkcert でローカル CA を構築し、カスタムドメイン(*.local)に証明書を発行し、Docker Compose 上の nginx をリバースプロキシとして立てる手順を説明します。

環境

  • macOS Sonoma 14.x
  • mkcert v1.4.4
  • nginx 1.29.8(Docker: nginx:alpine)
  • Docker 28.5.1

mkcert とは

HTTPS の証明書には「信頼された認証局(CA)が署名している」という条件があります。Let's Encrypt などの公的 CA は実在するドメインにしか証明書を発行しないため、app.local のようなローカル専用ドメインには使えません。

openssl で自己署名の証明書を作る方法もあります。ただしその場合、ブラウザーが安全でない接続として警告を出し続けます。毎回例外を追加する必要があるうえ、スクリーンリーダーや自動テストで例外を追加できないため実用になりません。

mkcert はこの問題を解決するツールで、ローカル開発専用の CA を作り、OS の信頼ストアに登録します。一度登録すれば、mkcert が発行した証明書はブラウザーに「正規の CA が署名した証明書」として認識されます。警告は出ません。

インストール

# macOS
brew install mkcert nss   # nss は Firefox 対応に必要

# Linux(Debian/Ubuntu)
apt install libnss3-tools
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert

ローカル CA の作成と登録

mkcert -install

このコマンドの動作は次のとおりです。

  1. ~/.local/share/mkcert/(Linux)または ~/Library/Application Support/mkcert/(macOS)に CA 鍵ペアを生成します
  2. OS の証明書ストアに CA を追加します(macOS はキーチェーン、Linux は ~/.pki/nssdb)
  3. Firefox の NSS データベースにも追加します(nss がインストールされている場合)

CA ファイルの場所は mkcert -CAROOT で確認できます。この CA 鍵を リポジトリへコミットしてはいけません。

証明書の発行

開発で使うドメインを列挙して一度に発行できます。

mkdir -p certs
cd certs
mkcert app.local api.local admin.local

生成されるファイルは次のとおりです。

certs/
├── app.local+2.pem       # 証明書
└── app.local+2-key.pem   # 秘密鍵

ファイル名がドメイン数に応じて変わると扱いにくいため、固定名にリネームします。

mv app.local+2.pem     local.pem
mv app.local+2-key.pem local-key.pem

証明書の有効期限は約 2 年(mkcert のデフォルト)です。

certs/ は .gitignore に追加しておきます。

# .gitignore
certs/*.pem
certs/*-key.pem

/etc/hosts の設定

app.local のようなドメインは DNS サーバーに登録されていないため、そのままでは名前解決できません。/etc/hosts に書くと、OS は DNS を引く前にこのファイルを参照し、指定した IP アドレスを返します。

向き先には 127.0.0.1(ループバックアドレス)を使います。ループバックアドレスとは「自分自身を指す特殊な IP アドレス」で、このアドレス宛のパケットはネットワークに出ず、OS 内部で折り返されます。ローカルで起動しているサーバーに繋ぐための慣習的なアドレスで、localhost もこのアドレスの別名として定義されています。

echo "127.0.0.1  app.local api.local admin.local" | sudo tee -a /etc/hosts

確認します。

ping -c 1 app.local
# PING app.local (127.0.0.1): 56 data bytes

nginx の設定

Docker Compose で nginx をリバースプロキシとして立てます。開発サーバーはホストで動かし、nginx はコンテナーから host.docker.internal 経由でアクセスします。

リクエストの流れは以下のとおりです。nginx がコンテナー内で HTTPS を終端し、ホスト側で動いている開発サーバーに HTTP で転送します。

ディレクトリ構成

project/
├── docker/
│   └── nginx/
│       └── nginx.conf
├── certs/
│   ├── local.pem
│   └── local-key.pem
└── docker-compose.dev.yml

nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
  worker_connections 1024;
}

http {
  # WebSocket の Upgrade ヘッダーを正規化
  map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
  }

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!aNULL:!MD5;
  ssl_prefer_server_ciphers on;

  # 証明書は一か所に集約
  ssl_certificate     /etc/nginx/certs/local.pem;
  ssl_certificate_key /etc/nginx/certs/local-key.pem;

  proxy_http_version 1.1;
  proxy_set_header Host              $host;
  proxy_set_header X-Real-IP         $remote_addr;
  proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header Upgrade           $http_upgrade;
  proxy_set_header Connection        $connection_upgrade;

  # app.local → ホストの :3000 番ポート
  server {
    listen 8443 ssl;
    server_name app.local;

    location / {
      proxy_pass http://host.docker.internal:3000;
      proxy_buffering off;
    }
  }

  # api.local → ホストの :8080 番ポート
  server {
    listen 8443 ssl;
    server_name api.local;

    location / {
      proxy_pass http://host.docker.internal:8080;
      proxy_buffering off;
    }
  }
}

docker-compose.dev.yml

services:
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - '127.0.0.1:8443:8443'
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    extra_hosts:
      - 'host.docker.internal:host-gateway'  # Linux での host 名解決
    healthcheck:
      test: ['CMD-SHELL', 'nginx -t 2>/dev/null && echo ok']
      interval: 10s
      timeout: 5s
      retries: 5

ports を 127.0.0.1:8443:8443 と指定することで、外部ネットワークからのアクセスをブロックできます。

起動

docker compose -f docker-compose.dev.yml up -d

アプリを :3000 で起動した状態で https://app.local:8443 にアクセスすると、証明書警告なしに HTTPS で繋がります。

動作確認

curl でも確認できます。--resolve オプションで DNS をオーバーライドします。

# 証明書エラーが出ないことを確認(-sk を使わない)
curl -v --resolve app.local:8443:127.0.0.1 https://app.local:8443/

レスポンスの中に以下が含まれていれば証明書は正常です。

* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* Server certificate:
*  subject: O=mkcert development certificate; ...
*  issuer: O=mkcert development CA; ...

セットアップスクリプトの例

チームで同じ環境を再現するために、スクリプトにまとめておくと便利です。

#!/usr/bin/env bash
# scripts/setup-tls.sh
set -euo pipefail

CERTS_DIR="$(cd "$(dirname "$0")/.." && pwd)/certs"
DOMAINS=(app.local api.local admin.local)

if ! command -v mkcert &>/dev/null; then
  echo "Error: mkcert が未インストールです。brew install mkcert nss"
  exit 1
fi

# CA が未登録なら登録
CA_ROOT=$(mkcert -CAROOT)
if [[ ! -f "${CA_ROOT}/rootCA.pem" ]]; then
  mkcert -install
fi

mkdir -p "${CERTS_DIR}"
cd "${CERTS_DIR}"
mkcert "${DOMAINS[@]}"

COUNT=$(( ${#DOMAINS[@]} - 1 ))
mv "${DOMAINS[0]}+${COUNT}.pem"     local.pem
mv "${DOMAINS[0]}+${COUNT}-key.pem" local-key.pem

echo "✓ ${CERTS_DIR}/local.pem"
echo "✓ ${CERTS_DIR}/local-key.pem"

# /etc/hosts への追記
for domain in "${DOMAINS[@]}"; do
  if ! grep -qE "^127\.[0-9.]+\s+${domain}" /etc/hosts; then
    echo "127.0.0.1  ${domain}" | sudo tee -a /etc/hosts >/dev/null
    echo "追加: ${domain}"
  fi
done

まとめ

役割 ツール
ローカル CA の作成・登録 mkcert
ドメイン証明書の発行 mkcert
DNS 解決 /etc/hosts
HTTPS 終端 + リバースプロキシ nginx(Docker)

mkcert -install は開発マシン 1 台につき 1 回だけ実行すれば十分です。証明書は mkcert で発行するたびに新しいものが作られます。複数のプロジェクトで同じ CA を共有できるため、2 つ目以降のプロジェクトでは mkcert -install は不要です。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?