はじめに
この記事は、前半記事の続きです。
前半では、Claude CodeからRaspberry PiへのSSH接続設定を行いました。
今回は、独自ドメインでHTTPS対応の自宅サーバーを構築する手順を、初心者の方でもClaude Codeなしで再現できるよう、詳しく解説します。
この記事で実現できること
- www.example.com → React + Viteアプリ
- seisan.example.com → LINE Bot(精算くん)
- bus.example.com → LINE Bot(バス情報)
すべてHTTPSで安全にアクセス可能、ngrokなしで独自ドメインで公開できます。
この記事ではドメイン名・IPアドレス・ユーザー名などをすべて伏せ字にしています。ご自身の環境に合わせて読み替えてください。
- ドメイン:
example.com - ラズパイの内部IP:
192.168.x.x - グローバルIP:
xxx.xxx.xxx.xxx - DDNSドメイン:
yourname.tplinkdns.com - ラズパイのユーザー名:
myuser
所要時間
- 設定作業: 約2〜3時間
- DNS浸透待ち: 30分〜1時間
構成環境
| 項目 | 内容 |
|---|---|
| ラズパイ | Raspberry Pi 3 |
| OS | Raspberry Pi OS (Debian bookworm) |
| Webサーバー | Nginx 1.22.1 |
| ランタイム | Node.js 18.x、Python 3.11 |
| ルーター | TP-Link Deco(DDNS機能あり) |
| ドメイン取得 | お名前.com |
| SSL証明書 | acme.sh + ZeroSSL(ワイルドカード証明書) |
前提条件
以下が完了していることを確認してください:
- 前半記事のSSH接続設定が完了
-
独自ドメインを取得済み(例:
example.com) - ルーターにDDNS機能がある(またはグローバル固定IP)
-
各サービスがラズパイのローカルポートで動作中
- 例: Vite (5173)、バックエンド (3001)、LINE Bot (3000, 5000)
📋 全体の流れ
1. ネットワーク設定
├─ ラズパイの静的IP予約
├─ DDNS設定(動的IP対策)
└─ ポートフォワーディング(80, 443を開放)
2. DNS設定(お名前.com)
├─ サブドメインのCNAMEレコード追加
└─ DNS浸透確認
3. Nginx設定(HTTP)
├─ Nginxインストール
├─ サイトごとの設定ファイル作成
└─ HTTP動作確認
4. SSL証明書取得(acme.sh)
├─ acme.shインストール
├─ DNS Challengeでワイルドカード証明書取得
└─ 証明書をNginxに配置
5. Nginx HTTPS化
├─ 各サイトにHTTPS設定追加
└─ HTTPS動作確認
6. LINE Bot Webhook変更
├─ LINE DevelopersでWebhook URL更新
└─ ngrok停止
7. セキュリティ設定
├─ ufwファイアウォール有効化
└─ ルーター側セキュリティ機能ON
8. 自動更新設定
└─ SSL証明書の自動更新確認
ステップ1: ネットワーク設定
1-1. ラズパイの静的IP予約
なぜ必要?
ラズパイのIPアドレスが変わると、ルーターのポートフォワーディング設定が無効になります。
手順(TP-Link Decoアプリの場合):
- スマホでDecoアプリを開く
- 画面下部の「デバイス」タブをタップ
- デバイス一覧から「Raspberry Pi」を探してタップ
- 「アドレス予約」をタップ
- スイッチをONにする
- IPアドレスを
192.168.x.xに設定(好きな値を選んでOK) - 保存ボタンをタップ
確認方法:
# Macから実行
ping 192.168.x.x
# 応答があればOK
1-2. DDNS設定
なぜ必要?
家庭用インターネットは通常、グローバルIPが定期的に変わります。DDNSを使うと、IPが変わっても常に同じドメイン名でアクセスできます。
手順(TP-Link Decoアプリの場合):
- Decoアプリを開く
- 画面下部の「その他」タブをタップ
- 「詳細設定」→「DDNS」をタップ
- 「TP-Link DDNS」をONにする
- サブドメイン名を入力(例:
yourname)- 完成形:
yourname.tplinkdns.com
- 完成形:
- 保存ボタンをタップ
確認方法:
# 現在のグローバルIPを確認
curl ifconfig.me
# DDNSドメインが正しいIPを返すか確認
ping yourname.tplinkdns.com
# 上記のグローバルIPと一致すればOK
注意:
- サブドメイン名は他の人と重複しない名前にしてください
- 設定後、反映まで数分かかる場合があります
1-3. ポートフォワーディング設定
なぜ必要?
デフォルトでは、外部からのアクセスはルーターでブロックされます。ポートフォワーディングで「外部ポート80/443への接続をラズパイに転送する」設定をします。
手順(TP-Link Decoアプリの場合):
- Decoアプリを開く
- 「その他」→「詳細設定」→「ポートフォワーディング」
- 右上の「+」ボタンをタップ
-
1つ目のルール(HTTP):
- サービス名:
HTTP - 外部ポート:
80 - 内部IP:
192.168.x.x(ラズパイのIP) - 内部ポート:
80 - プロトコル:
TCP - 保存
- サービス名:
-
2つ目のルール(HTTPS):
- サービス名:
HTTPS - 外部ポート:
443 - 内部IP:
192.168.x.x - 内部ポート:
443 - プロトコル:
TCP - 保存
- サービス名:
確認方法:
# 外部からポート80にアクセスできるか確認
curl -I http://$(curl -s ifconfig.me)
# エラーが返ってくればOK(まだNginxを設定していないため)
よくあるミス:
- ❌ 内部IPを間違える(必ずラズパイのIPを指定)
- ❌ プロトコルをUDPにしてしまう(必ずTCP)
- ❌ ルーターの設定画面にアクセスできない場合
- → ルーターの管理画面のIPアドレスを確認(通常192.168.1.1か192.168.0.1)
ステップ2: DNS設定(お名前.com)
2-1. DNSレコード設定
なぜこの設定?
- CNAME: サブドメイン(www, seisan, bus)をDDNSドメインに紐付け
- A: ルートドメイン(@)は直接グローバルIPを指定
手順:
- お名前.com Navi にログイン
- 「ドメイン」タブをクリック
- 対象ドメイン(例: example.com)の「DNS」をクリック
- 「DNS設定/転送設定」→「DNSレコード設定を利用する」
- 以下のレコードを1つずつ追加:
| ホスト名 | TYPE | TTL | VALUE | 優先 |
|---|---|---|---|---|
| www | CNAME | 3600 | yourname.tplinkdns.com | - |
| seisan | CNAME | 3600 | yourname.tplinkdns.com | - |
| bus | CNAME | 3600 | yourname.tplinkdns.com | - |
| (空欄) | A | 3600 | (グローバルIP) | - |
グローバルIPの確認方法:
curl ifconfig.me
# 例: xxx.xxx.xxx.xxx
入力例(www の場合):
ホスト名: www
TYPE: CNAME
TTL: 3600
VALUE: yourname.tplinkdns.com
重要な注意点:
- ✅ VALUEの末尾に
.(ドット) は不要(お名前.comの場合) - ✅ ホスト名が「@」の場合は空欄にする
- ✅ 既存のレコードがある場合は削除してから追加
- すべて入力したら「確認画面へ進む」→「設定する」
2-2. DNS浸透確認
なぜ待つ必要がある?
DNS設定は世界中のDNSサーバーに伝わるまで時間がかかります(通常30分〜1時間)。
確認コマンド:
# Google DNS (8.8.8.8) に問い合わせ
dig www.example.com @8.8.8.8
成功例(出力の一部):
;; ANSWER SECTION:
www.example.com. 3600 IN CNAME yourname.tplinkdns.com.
yourname.tplinkdns.com. 600 IN A xxx.xxx.xxx.xxx
浸透していない場合:
;; ANSWER SECTION:
(何も表示されない、またはパーキングページのIP)
待ち時間中にできること:
- 次のステップ3(Nginx設定)を先に進めておく
- コーヒーを飲む ☕️
ステップ3: Nginx設定(HTTP)
3-1. Nginxインストール
Nginxとは?
Webサーバーソフトウェア。複数のアプリケーションを1つのサーバーで動かすための「振り分け役」です。
# MacのターミナルからSSH接続
ssh raspi
# ラズパイ上で実行
sudo apt update
sudo apt install -y nginx
インストール確認:
nginx -v
# nginx version: nginx/1.22.1 のように表示されればOK
Nginxの起動と自動起動設定:
sudo systemctl start nginx
sudo systemctl enable nginx
3-2. サイト別の設定ファイル作成
Nginxの設定ファイルの仕組み:
-
/etc/nginx/sites-available/→ 設定ファイルを保存する場所 -
/etc/nginx/sites-enabled/→ 有効な設定へのシンボリックリンク
📄 www.example.com 用の設定
sudo nano /etc/nginx/sites-available/zamurai
nanoエディタの使い方:
- 貼り付け:
Ctrl + Shift + V(MacはCmd + V) - 保存:
Ctrl + O→ Enter - 終了:
Ctrl + X
貼り付ける内容:
server {
listen 80;
server_name www.example.com;
# Let's Encrypt の検証用(後で使う)
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
# /api/ で始まるリクエストはバックエンド(3001番ポート)へ
location /api/ {
proxy_pass http://127.0.0.1:3001/api/;
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;
}
# /ws で始まるリクエストはWebSocketへ
location /ws {
proxy_pass http://127.0.0.1:3001/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# その他のリクエストはVite(5173番ポート)へ
location / {
proxy_pass http://127.0.0.1:5173;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
設定の意味:
-
listen 80;→ HTTPポート(80番)で待ち受け -
server_name→ このドメインでアクセスされたら、この設定を使う -
proxy_pass→ 実際のアプリケーションに転送 -
proxy_set_header→ 元のリクエスト情報をアプリに伝える
📄 seisan.example.com 用の設定
sudo nano /etc/nginx/sites-available/seisan
server {
listen 80;
server_name seisan.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
ポイント:
- 精算くんは3000番ポートで動いているので
proxy_pass http://127.0.0.1:3000;
📄 bus.example.com 用の設定
sudo nano /etc/nginx/sites-available/bus
server {
listen 80;
server_name bus.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
location / {
proxy_pass http://127.0.0.1:5000;
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;
}
}
ポイント:
- バス情報は5000番ポートで動いているので
proxy_pass http://127.0.0.1:5000;
📄 ルートドメインのリダイレクト設定
sudo nano /etc/nginx/sites-available/redirect
server {
listen 80;
server_name example.com;
# example.com でアクセスされたら www にリダイレクト
return 301 https://www.example.com$request_uri;
}
3-3. 設定の有効化
シンボリックリンクとは?
「ショートカット」のようなもの。sites-available/ の設定ファイルを sites-enabled/ からリンクして有効化します。
# 1つずつシンボリックリンクを作成
sudo ln -s /etc/nginx/sites-available/zamurai /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/seisan /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/bus /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/redirect /etc/nginx/sites-enabled/
既にリンクがある場合:
# エラーが出たら、既存のリンクを削除してから再実行
sudo rm /etc/nginx/sites-enabled/zamurai
sudo ln -s /etc/nginx/sites-available/zamurai /etc/nginx/sites-enabled/
3-4. Nginx設定テストと再起動
必ず実行してください!
設定ミスがあるとNginxが起動しなくなります。
# 設定ファイルの文法チェック
sudo nginx -t
成功例:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
エラーが出た場合:
nginx: [emerg] unexpected "}" in /etc/nginx/sites-available/zamurai:10
→ 10行目付近に余分な } があります。設定ファイルを見直してください。
問題なければNginx再起動:
sudo systemctl reload nginx
3-5. HTTP動作確認
ローカル(ラズパイ上)からの確認:
# ラズパイ上で実行
curl http://localhost
# HTMLが返ってくればOK
外部(Mac)からの確認:
# Macのターミナルで実行
curl http://www.example.com
よくあるエラーと対処法:
| エラー | 原因 | 対処法 |
|---|---|---|
Connection refused |
Nginxが起動していない | sudo systemctl start nginx |
502 Bad Gateway |
バックエンドアプリが起動していない | アプリを起動 |
404 Not Found |
server_nameが一致していない | 設定ファイルのserver_nameを確認 |
ステップ4: SSL証明書取得(acme.sh + DNS Challenge)
4-1. acme.shのインストール
acme.shとは?
SSL証明書を無料で取得・更新するツール。Let's Encrypt / ZeroSSL に対応しています。
# ラズパイにSSH接続した状態で実行
curl https://get.acme.sh | sh -s email=your-email@example.com
⚠️ your-email@example.com を自分のメールアドレスに変更してください
インストール後の設定反映:
source ~/.bashrc
インストール確認:
~/.acme.sh/acme.sh --version
# https://github.com/acmesh-official/acme.sh
# v3.1.3 のように表示されればOK
4-2. DNS Challengeでワイルドカード証明書取得
なぜDNS Challenge?
- ❌ HTTP Challenge: プロキシ環境で失敗しやすい、サブドメインごとに証明書が必要
- ✅ DNS Challenge: どんな環境でも動作、ワイルドカード証明書(
*.example.com)が取得できる
ワイルドカード証明書のメリット:
- 1つの証明書で
www.,seisan.,bus.すべてカバー - 新しいサブドメインを追加しても証明書の再取得が不要
ステップ4-2-1: 証明書発行開始
~/.acme.sh/acme.sh --issue --dns -d example.com -d "*.example.com"
実行すると、以下のような出力が表示されます:
Add the following TXT record:
Domain: '_acme-challenge.example.com'
TXT value: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
Add the following TXT record:
Domain: '_acme-challenge.example.com'
TXT value: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
Please add the TXT records to the domains, and re-run with --renew.
⚠️ 重要: この値は毎回変わります!必ず表示された値をメモしてください
ステップ4-2-2: お名前.comでTXTレコード追加
手順:
- お名前.com Navi にログイン
- 「ドメイン」→ 対象ドメインの「DNS」
- 「DNSレコード設定を利用する」
-
1つ目のTXTレコードを追加:
- ホスト名:
_acme-challenge - TYPE:
TXT - TTL:
3600 - VALUE: (acme.shで表示された1つ目の値)
- ホスト名:
-
2つ目のTXTレコードも同様に追加:
- ホスト名:
_acme-challenge - TYPE:
TXT - TTL:
3600 - VALUE: (acme.shで表示された2つ目の値)
- ホスト名:
- 「確認画面へ進む」→「設定する」
よくあるミス:
- ❌ VALUEの前後にスペースを入れてしまう
- ❌ シングルクォート
'を入力してしまう(不要) - ❌ 1つしか追加しない(2つとも必要)
ステップ4-2-3: DNS浸透確認
なぜ待つ?
TXTレコードが世界中のDNSサーバーに伝わるまで待ちます(通常30秒〜5分)。
# Google DNS (8.8.8.8) に問い合わせ
dig _acme-challenge.example.com TXT @8.8.8.8
成功例(2つのTXTレコードが表示される):
;; ANSWER SECTION:
_acme-challenge.example.com. 3600 IN TXT "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
_acme-challenge.example.com. 3600 IN TXT "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
まだ浸透していない場合:
- 何も表示されない、または1つしか表示されない
- → 数分待ってから再度実行
浸透を早める方法:
# 複数のDNSサーバーで確認
dig _acme-challenge.example.com TXT @8.8.8.8 # Google
dig _acme-challenge.example.com TXT @1.1.1.1 # Cloudflare
dig _acme-challenge.example.com TXT @208.67.222.222 # OpenDNS
ステップ4-2-4: 証明書発行完了
TXTレコードが浸透したら実行:
~/.acme.sh/acme.sh --renew -d example.com -d "*.example.com"
成功すると、以下のような出力が表示されます:
[Mon Mar 22 15:45:30 JST 2026] Cert success.
-----BEGIN CERTIFICATE-----
MIIFZTCCBEygAwIBAgISBN...(省略)
-----END CERTIFICATE-----
[Mon Mar 22 15:45:30 JST 2026] Your cert is in: /home/myuser/.acme.sh/example.com_ecc/example.com.cer
[Mon Mar 22 15:45:30 JST 2026] Your cert key is in: /home/myuser/.acme.sh/example.com_ecc/example.com.key
[Mon Mar 22 15:45:30 JST 2026] The intermediate CA cert is in: /home/myuser/.acme.sh/example.com_ecc/ca.cer
[Mon Mar 22 15:45:30 JST 2026] And the full chain certs is there: /home/myuser/.acme.sh/example.com_ecc/fullchain.cer
証明書が保存されたパス:
/home/myuser/.acme.sh/example.com_ecc/
├── example.com.cer ← ドメイン証明書
├── example.com.key ← 秘密鍵(絶対に公開しない)
├── ca.cer ← 中間証明書
└── fullchain.cer ← 証明書チェーン(Nginxで使用)
エラーが出た場合:
| エラー | 原因 | 対処法 |
|---|---|---|
Verify error |
TXTレコードが見つからない | DNS浸透を再確認 |
Invalid domain |
ドメイン名のタイポ |
-d の値を確認 |
Rate limit exceeded |
同じドメインで何度も失敗した | 1時間待ってから再実行 |
4-3. 証明書をNginxにコピー
なぜコピーが必要?
証明書はユーザーのホームディレクトリにありますが、Nginxは通常rootで動作します。Nginxが読み込める場所にコピーします。
# SSL証明書用ディレクトリを作成
sudo mkdir -p /etc/nginx/ssl
# 証明書を一時ディレクトリにコピー(ユーザー権限で実行)
cp ~/.acme.sh/example.com_ecc/*.cer /tmp/
cp ~/.acme.sh/example.com_ecc/*.key /tmp/
# 一時ディレクトリからNginxのディレクトリに移動(root権限で実行)
sudo mv /tmp/fullchain.cer /etc/nginx/ssl/
sudo mv /tmp/example.com.cer /etc/nginx/ssl/
sudo mv /tmp/example.com.key /etc/nginx/ssl/
# パーミッション設定(セキュリティ対策)
sudo chmod 600 /etc/nginx/ssl/example.com.key # 秘密鍵は所有者のみ読み取り可
sudo chmod 644 /etc/nginx/ssl/*.cer # 証明書は誰でも読み取り可
確認:
ls -la /etc/nginx/ssl/
出力例:
-rw-r--r-- 1 root root 3749 Mar 22 15:50 fullchain.cer
-rw-r--r-- 1 root root 2156 Mar 22 15:50 example.com.cer
-rw------- 1 root root 227 Mar 22 15:50 example.com.key
重要: 秘密鍵(.key)のパーミッションが 600 になっていることを確認してください
ステップ5: Nginx HTTPS設定
HTTPS設定の基本方針
各サイトで以下の構成にします:
- HTTPサーバー(ポート80): HTTPSにリダイレクト
- HTTPSサーバー(ポート443): 実際のアプリケーションに転送
5-1. www.example.com をHTTPS化
sudo nano /etc/nginx/sites-available/zamurai
既存の内容をすべて削除して、以下に置き換えます:
# HTTP → HTTPS リダイレクト
server {
listen 80;
server_name www.example.com;
# Let's Encrypt の検証用(証明書更新時に使用)
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
# その他のリクエストはすべてHTTPSにリダイレクト
return 301 https://$server_name$request_uri;
}
# HTTPS サーバー
server {
listen 443 ssl http2;
server_name www.example.com;
# SSL証明書のパス
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# SSL設定(セキュリティ強化)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
# Let's Encrypt の検証用(HTTPSでも必要)
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
# /api/ で始まるリクエストはバックエンドへ
location /api/ {
proxy_pass http://127.0.0.1:3001/api/;
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;
}
# WebSocket接続
location /ws {
proxy_pass http://127.0.0.1:3001/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# その他のリクエストはViteへ
location / {
proxy_pass http://127.0.0.1:5173;
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;
}
}
保存して終了: Ctrl + O → Enter → Ctrl + X
5-2. seisan.example.com をHTTPS化
sudo nano /etc/nginx/sites-available/seisan
既存の内容をすべて削除して、以下に置き換えます:
# HTTP → HTTPS リダイレクト
server {
listen 80;
server_name seisan.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS サーバー
server {
listen 443 ssl http2;
server_name seisan.example.com;
# SSL証明書(ワイルドカード証明書を使用)
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# SSL設定
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
# プロキシ設定(LINE Bot バックエンド)
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
5-3. bus.example.com をHTTPS化
sudo nano /etc/nginx/sites-available/bus
既存の内容をすべて削除して、以下に置き換えます:
# HTTP → HTTPS リダイレクト
server {
listen 80;
server_name bus.example.com;
return 301 https://$server_name$request_uri;
}
# HTTPS サーバー
server {
listen 443 ssl http2;
server_name bus.example.com;
# SSL証明書(ワイルドカード証明書を使用)
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# SSL設定
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
}
# プロキシ設定(LINE Bot バックエンド)
location / {
proxy_pass http://127.0.0.1:5000;
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;
}
}
5-4. ルートドメインもHTTPS化
sudo nano /etc/nginx/sites-available/redirect
既存の内容をすべて削除して、以下に置き換えます:
# HTTP → HTTPS リダイレクト
server {
listen 80;
server_name example.com;
return 301 https://www.example.com$request_uri;
}
# HTTPS でも www にリダイレクト
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
return 301 https://www.example.com$request_uri;
}
5-5. Nginx設定テストと再読み込み
必ず実行してください!
# 設定ファイルのテスト
sudo nginx -t
成功例:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
問題なければ再読み込み:
sudo systemctl reload nginx
よくあるエラー:
| エラー | 原因 | 対処法 |
|---|---|---|
cannot load certificate |
証明書のパスが間違っている |
ls /etc/nginx/ssl/ で確認 |
PEM_read_bio_X509_AUX() failed |
証明書ファイルが壊れている | 証明書を再取得 |
nginx: [emerg] bind() to 0.0.0.0:443 failed |
既に443番ポートが使用中 |
sudo lsof -i :443 で確認 |
5-6. HTTPS動作確認
ターミナルからの確認:
# Macのターミナルから実行
curl -I https://www.example.com
curl -I https://seisan.example.com
curl -I https://bus.example.com
成功例:
HTTP/2 200
server: nginx/1.22.1
date: Sun, 22 Mar 2026 15:54:27 GMT
content-type: text/html
ブラウザからの確認:
- ブラウザで
https://www.example.comにアクセス - URLバーに「🔒」マークが表示されればOK
- 鍵マークをクリック → 「証明書」→ 発行者が「ZeroSSL」になっていることを確認
HTTPからのリダイレクト確認:
curl -I http://www.example.com
成功例(301リダイレクト):
HTTP/1.1 301 Moved Permanently
Server: nginx/1.22.1
Location: https://www.example.com/
ステップ6: LINE Bot Webhook URL変更
6-1. LINE Webhook URLの仕組み
現在の状態:
- LINE → ngrokのURL(例:
https://xxxx.ngrok-free.app/webhook)→ ラズパイ
変更後:
- LINE → 独自ドメイン(例:
https://seisan.example.com/webhook)→ ラズパイ
6-2. エンドポイントの確認
重要: LINE Botごとにエンドポイントが異なります
アプリのソースコードでWebhookのエンドポイントを確認してください。
- 精算くん →
/webhook - バス情報 →
/callback
のように、アプリによって異なります。
6-3. LINE Developersコンソールでの設定
精算くんの設定
- LINE Developers Console にログイン
- 「プロバイダー」→ 対象のプロバイダーを選択
- 「精算くん」のチャネルをクリック
- 左メニューの「Messaging API」をクリック
- 「Webhook URL」の項目を探す
- 「編集」ボタンをクリック
- 新しいURLを入力:
https://seisan.example.com/webhook - 「更新」ボタンをクリック
- 「検証」ボタンをクリック
成功メッセージ:
成功しました
エラーが出た場合:
-
接続できませんでした→ Nginxの設定を確認、バックエンドが起動しているか確認 -
無効なHTTPステータスコード→ アプリ側で200を返しているか確認
バス情報の設定
- 同様にLINE Developersコンソールで「バス情報」のチャネルを開く
- Webhook URLを変更:
⚠️
https://bus.example.com/callback/callbackであることに注意(アプリによってエンドポイントが異なる) - 「更新」→「検証」
6-4. 実際にLINEから動作確認
精算くんの確認:
- LINEアプリで精算くんのトークを開く
- 何かメッセージを送信
- 返信があればOK
バス情報の確認:
- LINEアプリでバス情報のトークを開く
- バス停の情報をリクエスト
- 返信があればOK
6-5. ngrok停止
独自ドメインで正常動作することを確認したら、ngrokを停止します:
# ラズパイ上で実行
ps aux | grep ngrok | grep -v grep
プロセスIDをメモして停止:
kill <プロセスID>
確認:
ps aux | grep ngrok | grep -v grep
# 何も表示されなければOK
ngrokの自動起動設定がある場合は削除:
# cronを確認
crontab -l | grep ngrok
# もしngrokの起動コマンドがあれば削除
crontab -e
# 該当行を削除して保存
ステップ7: セキュリティ設定
7-1. ファイアウォール設定(ufw)
ufwとは?
Ubuntu/Debianで使える簡単なファイアウォールツール。不要なポートへのアクセスをブロックします。
ufwのインストール
# ラズパイ上で実行
sudo apt update
sudo apt install -y ufw
デフォルトポリシーの設定
# 基本方針: 受信はすべて拒否、送信はすべて許可
sudo ufw default deny incoming
sudo ufw default allow outgoing
意味:
-
deny incoming: 外部からの接続を拒否(ホワイトリスト方式) -
allow outgoing: ラズパイから外部への接続は許可
必要なポートのみ許可
# SSH(22番ポート)を許可
sudo ufw allow 22/tcp comment 'SSH'
# HTTP(80番ポート)を許可
sudo ufw allow 80/tcp comment 'HTTP'
# HTTPS(443番ポート)を許可
sudo ufw allow 443/tcp comment 'HTTPS'
なぜHTTPも許可?
- HTTPSへのリダイレクトに使用
- SSL証明書の更新(HTTP Challenge)に必要
ファイアウォール有効化
sudo ufw enable
警告メッセージが表示されます:
Command may disrupt existing ssh connections. Proceed with operation (y|n)?
y を入力してEnter
⚠️ SSH接続が切れるリスクがあります
- ただし、ポート22を許可しているので通常は問題ありません
- もし接続が切れたら、再度
ssh raspiで接続してください
設定確認
sudo ufw status verbose
出力例:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere # SSH
80/tcp ALLOW IN Anywhere # HTTP
443/tcp ALLOW IN Anywhere # HTTPS
22/tcp (v6) ALLOW IN Anywhere (v6) # SSH
80/tcp (v6) ALLOW IN Anywhere (v6) # HTTP
443/tcp (v6) ALLOW IN Anywhere (v6) # HTTPS
IPv6版のルールも自動的に追加されます
外部から動作確認
# Macのターミナルから実行
curl -I https://www.example.com
# 正常に返ってくればOK
7-2. ルーター側のセキュリティ設定
二重のセキュリティ対策を実施します:
- ルーター側(Deco): ネットワークレベルでの防御
- サーバー側(ufw): ポートレベルでの防御
TP-Link Decoのセキュリティ機能を有効化
手順:
- Decoアプリを開く
- 「その他」→「詳細設定」→「セキュリティ」
- 以下3つをすべてONにする:
① 悪意あるコンテンツフィルタ
- フィッシングサイトやマルウェア配布サイトへのアクセスをブロック
- 影響: ほとんどなし(誤検知は稀)
② 侵入防止システム (IPS)
- 外部からの不正アクセス試行を検知・ブロック
- 影響: 正常なHTTPS通信が誤検知される可能性(低い)
③ 感染したデバイスの隔離
- マルウェア感染が疑われるデバイスを自動で隔離
- 影響: 誤検知でラズパイが隔離される可能性(低い)
動作確認
ONにした後、すぐに確認:
# Macのターミナルから実行
curl -I https://www.example.com
curl -I https://seisan.example.com
curl -I https://bus.example.com
すべて正常に返ってくればOK
もし誤検知でブロックされた場合:
- Decoアプリ → 「セキュリティ」→「ブロックされた接続」で確認
- 該当の機能を一時的にOFFにして原因を特定
7-3. 定期的なセキュリティメンテナンス
システムアップデート(月1回推奨)
ssh raspi
sudo apt update
sudo apt upgrade -y
Nginxのアクセスログで監視
# リアルタイムでアクセスログを監視
sudo tail -f /var/log/nginx/access.log
注目すべきログ:
-
404が大量: ポートスキャンの可能性 - 知らないIPから大量アクセス: 攻撃の可能性
ステップ8: SSL証明書の自動更新設定
8-1. acme.shの自動更新確認
acme.shは自動的にcronに更新タスクを追加します:
crontab -l | grep acme
出力例:
28 0 * * * "/home/myuser/.acme.sh"/acme.sh --cron --home "/home/myuser/.acme.sh" > /dev/null
意味:
- 毎日午前0時28分に証明書の更新チェックを実行
- 証明書が期限切れ間近(30日以内)なら自動更新
8-2. 手動更新テスト
実際に更新できるか確認:
~/.acme.sh/acme.sh --renew -d example.com -d "*.example.com" --force
--force オプション:
- 期限に関係なく強制的に更新
- テスト用(本番では不要)
成功例:
[Mon Mar 22 16:00:00 JST 2026] Renew: 'example.com'
[Mon Mar 22 16:00:05 JST 2026] Renew success.
8-3. Nginx自動リロードの設定
証明書が更新されたらNginxを自動リロードする設定:
~/.acme.sh/acme.sh --install-cert -d example.com \
--cert-file /etc/nginx/ssl/example.com.cer \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/fullchain.cer \
--reloadcmd "sudo systemctl reload nginx"
意味:
- 証明書が更新されたら、自動的に
/etc/nginx/ssl/にコピー - その後、Nginxをリロード
⚠️ sudoのパスワードなし実行が必要:
# sudoersファイルを編集
sudo visudo
以下の行を最後に追加:
myuser ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx
保存して終了: Ctrl + X → Y → Enter
8-4. TXTレコードの削除(オプション)
証明書取得が完了したら、TXTレコードは削除してOKです:
- お名前.com管理画面 → DNS設定
-
_acme-challengeのTXTレコード2つを削除 - 保存
注意:
- 削除しても問題ありません(更新時は再度追加します)
- 残しておいても害はありません
トラブルシューティング
問題1: HTTPSでアクセスできない
症状
curl https://www.example.com
# curl: (35) error:0A000410:SSL routines::sslv3 alert handshake failure
原因と対策
| 原因 | 確認方法 | 対策 |
|---|---|---|
| 証明書のパーミッション不正 | ls -la /etc/nginx/ssl/ |
sudo chmod 600 /etc/nginx/ssl/*.key |
| 証明書のパスが間違っている | sudo nginx -t |
設定ファイルのパスを確認 |
| ポート443が閉じている | sudo lsof -i :443 |
sudo ufw allow 443/tcp |
| Nginxが起動していない | sudo systemctl status nginx |
sudo systemctl start nginx |
詳細確認方法
# SSL証明書の有効期限を確認
openssl x509 -in /etc/nginx/ssl/example.com.cer -noout -dates
# Nginxのエラーログを確認
sudo tail -50 /var/log/nginx/error.log
# ポート443が開いているか確認
sudo netstat -tlnp | grep 443
問題2: DNS Challenge が失敗する
症状
Verify error: Fetching http://.../.well-known/acme-challenge/...
原因
TXTレコードが浸透していない、または設定が間違っている
対策手順
1. TXTレコードの確認:
dig _acme-challenge.example.com TXT @8.8.8.8
2. 複数のDNSサーバーで確認:
dig _acme-challenge.example.com TXT @8.8.8.8 # Google
dig _acme-challenge.example.com TXT @1.1.1.1 # Cloudflare
dig _acme-challenge.example.com TXT @208.67.222.222 # OpenDNS
3. お名前.comの設定画面で再確認:
- ホスト名が
_acme-challengeであること - TYPEが
TXTであること - VALUEにシングルクォート
'が入っていないこと
4. 浸透まで待機(最大1時間)
問題3: LINE Bot Webhookが動作しない
症状
LINE Developersの検証で「接続できません」「タイムアウト」
原因と対策
| 原因 | 確認方法 | 対策 |
|---|---|---|
| エンドポイントのパス間違い | アプリのコードを確認 |
/webhook か /callback か確認 |
| バックエンドが起動していない | ps aux | grep node |
サービスを起動 |
| Nginxのproxy_pass設定ミス | Nginx設定ファイルを確認 | proxy_passのポート番号確認 |
| HTTPSの設定ミス | curl -I https://seisan.example.com |
HTTPS動作確認 |
詳細確認方法
1. ローカルでWebhookをテスト:
# ラズパイ上で実行
curl -X POST http://localhost:3000/webhook \
-H 'Content-Type: application/json' \
-d '{"events":[]}'
# 200 OKが返ってくればバックエンドは正常
2. Nginxのアクセスログを確認:
sudo tail -f /var/log/nginx/access.log
# LINE Developers から検証ボタンを押す
# ログに接続が記録されるか確認
問題4: iPhoneやMacから接続できない
症状
ドメインでアクセスすると「お名前.comのパーキングページ」が表示される
原因1: /etc/hosts にローカル設定が残っている
Macの場合:
# hostsファイルを確認
cat /etc/hosts | grep example
# 不要な行があれば削除
sudo nano /etc/hosts
# 該当行を削除して保存
# DNSキャッシュをクリア
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder
iPhoneの場合:
- 設定 → Wi-Fi → 接続中のネットワークの (i) マーク → 「DHCPリースを更新」
原因2: DNS浸透中
確認:
nslookup www.example.com
対策:
- 数時間待つ
- または、DNSサーバーを手動で 8.8.8.8 に変更
問題5: ファイアウォールでブロックされている
症状
curl https://www.example.com がタイムアウト
確認方法
# ufwの状態確認
sudo ufw status verbose
# 特定のポートが許可されているか確認
sudo ufw status | grep 443
対策
# ポート443を許可(もし許可されていなければ)
sudo ufw allow 443/tcp
# ufwをリロード
sudo ufw reload
# 確認
sudo ufw status
まとめ
お疲れさまでした!これで以下がすべて完成しました:
✅ 完成したこと
- 独自ドメインでの自宅サーバー公開
- ワイルドカード証明書によるHTTPS化(すべてのサブドメインで共通)
- Nginxリバースプロキシで複数サービスを管理
- LINE BotのWebhook接続(ngrok不要)
- ファイアウォールによる二重のセキュリティ対策
- SSL証明書の自動更新(90日ごと)
📊 アーキテクチャ図
インターネット
↓
[お名前.com DNS]
↓ (CNAME → DDNS)
[ルーター]
├─ DDNS (yourname.tplinkdns.com)
├─ ポートフォワーディング (80, 443)
└─ セキュリティ機能 (IPS, 悪意あるコンテンツフィルタ)
↓
[Raspberry Pi (192.168.x.x)]
├─ ufw ファイアウォール (22, 80, 443のみ許可)
├─ Nginx (リバースプロキシ)
│ ├─ www.example.com → :5173 (Vite) + :3001 (Backend)
│ ├─ seisan.example.com → :3000 (LINE Bot)
│ └─ bus.example.com → :5000 (LINE Bot)
└─ acme.sh (SSL証明書自動更新)
今後の改善案
1. systemdでサービス自動起動
現状の課題:
- ラズパイを再起動すると、手動でアプリを起動する必要がある
解決策:
sudo nano /etc/systemd/system/myapp-backend.service
[Unit]
Description=My Backend Service
After=network.target
[Service]
Type=simple
User=myuser
WorkingDirectory=/home/myuser/myapp/backend
ExecStart=/usr/bin/node src/server.js
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl enable myapp-backend
sudo systemctl start myapp-backend
2. fail2ban導入
目的:
- 不正なSSHログイン試行を自動ブロック
インストール:
sudo apt install -y fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
3. 監視ツール導入
無料で使えるサービス:
- UptimeRobot: サイトの死活監視(5分ごとにpingを送る)
- Prometheus + Grafana: サーバーのリソース監視
4. バックアップ自動化
重要なデータ:
- データベース
- Nginx設定ファイル
- SSL証明書
バックアップスクリプト例:
#!/bin/bash
DATE=$(date +%Y%m%d)
tar -czf /backup/nginx-$DATE.tar.gz /etc/nginx/
tar -czf /backup/ssl-$DATE.tar.gz /etc/nginx/ssl/
参考リンク
おわりに
この記事では、Raspberry Piを使った自宅サーバーの構築を、初心者の方でも再現できるよう詳しく解説しました。
Claude Codeに頼らず、自分の手で構築できるようになることを目指して、すべての手順に理由と確認方法を記載しました。
もし不明点やエラーが発生した場合は、コメント欄でお気軽にご質問ください。一緒に解決しましょう!
Happy Hacking! 🚀