自宅サーバにn8nとdifyをインストールしてHTTPS アクセスするまで

# やりたかったこと

* 自宅の Ubuntu 24.04 サーバに [n8n](https://n8n.io/) と [dify](https://github.com/langgenius/dify) をインストール
* Mac から HTTPS 経由でアクセス
* IP アドレス固定なし（mDNS で `n8n.local` と `dify.local` に名前解決）
* TLS 証明書は mkcert で自前発行（ブラウザ警告なし）

```plaintext
ブラウザ (https://dify.local) -> ホスト Nginx (443) -> dify (8081)
ブラウザ (https://n8n.local) ──► ホスト Nginx (443) -> n8n (5678)
```

# 経緯
- 同一PCでdifyとn8nを動かしており、それぞれにhttpsアクセスする必要があった
  - n8nはlocalhost以外はhttpsが必要なため
  - difyはマイク有効化にhttpsが必要なため
- 普通にhostname.localをhttps化するとport 443が干渉するため、nginxで個別のアドレスをルーティングするようにした

# 免責
- 色々試した中で成功に寄与したと思われる手順をピックアップしていますが、無駄な手順や間違いが含まれているかもしれません。ご了承ください。

# 手順

## n8n のインストール
### docker-compose作成
公式のdockerコマンドをdocker-composeで実行できるようにしておきます。

```bash
mkdir n8n
cd n8n
nano docker-compose.yaml
```

以下を記載

```yaml:docker-compose.yaml
services:
  n8n:
    image: n8nio/n8n:latest
    ports: ["5678:5678"]
    volumes: [n8n_data:/home/node/.n8n]
    environment:
      - N8N_PROTOCOL=http
      - N8N_HOST=n8n.local
      - N8N_PORT=5678
      - WEBHOOK_URL=https://n8n.local/
volumes:
  n8n_data: {}
```

environmentはChatGPTが含めるように教えてくれたのですが、本当に必要かは不明です。
### サービス化
systemdでサービス化しておきます。

```:/etc/systemd/system/n8n.service
[Unit]
Description=Start n8n via Docker Compose
After=docker.service network-online.target
Requires=docker.service

[Service]
WorkingDirectory=/path/to/docker/compose/dir/
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
Restart=always

[Install]
WantedBy=multi-user.target
```

```bash
# サービスに登録
sudo systemctl enable n8n
# 起動
sudo systemctl start n8n
# 状態確認
sudo systemctl status n8n
# 状態確認（curl）
curl localhost:5678
```

起動がうまくいっていなさそうであれば、`docker compose up`を直接実行するなどしてデバッグしてください。

## difyのインストール

[公式](https://github.com/langgenius/dify)のQuick Startに基本従いますが、portだけdocker-compose.override.yamlを作成して8081に変更しておきます。80はnginxが使うためです。

```bash
git clone https://github.com/langgenius/dify.git
cd dify/docker
cp .env.example .env
nano docker-compose.override.yaml
```

以下を記載します。

```yaml:docker-compose.override.yaml
services:
  nginx:
    ports:
      - "8081:80"   # ホストの 8081 にマッピング
```

## サービス化
n8n同様にsystemdでサービス化しておきます。
serviceファイルもほぼn8nと同じです。

```:/etc/systemd/system/n8n.service
[Unit]
Description=Start dify via Docker Compose
After=docker.service network-online.target
Requires=docker.service

[Service]
WorkingDirectory=/path/to/docker/compose/dir/
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
Restart=always

[Install]
WantedBy=multi-user.target
```

```bash
# サービスに登録
sudo systemctl enable dify
# 起動
sudo systemctl start dify
# 状態確認
sudo systemctl status dify
# 状態確認（curl）
curl localhost:8081
```

起動がうまくいっていなさそうであれば、`docker compose up`を直接実行するなどしてデバッグしてください。

```bash
docker compose up -d
```

## mkcertで自己署名CA+証明書発行

mkcertを使ってhttps://n8n.local および https://dify.local でアクセスするための自己署名CAと証明書を発行します。
作成されるファイル名はn8n.local*ですが、`mkcert n8n.local dify.local`のコマンドによりdifyの情報も合わせて書かれています。

```bash
sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert n8n.local dify.local
sudo mkdir -p /srv/ssl
sudo mv n8n.local+1.pem /srv/ssl/
sudo mv n8n.local+1-key.pem /srv/ssl/
```

## ホストOSのNginx設定

n8n.local、dify.localへのアクセスをルーティングする処理を書きます。

```bash
sudo apt install -y nginx
```

`/etc/nginx/sites-available/apps.conf` を作成：

```nginx:/etc/nginx/sites-available/apps.conf
# ---------- n8n ----------
server {
    listen 443 ssl http2;
    server_name n8n.local;

    ssl_certificate     /srv/ssl/n8n.local+1.pem;
    ssl_certificate_key /srv/ssl/n8n.local+1-key.pem;

    location / {
        proxy_pass         http://127.0.0.1:5678;   # n8n コンテナ
        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-Proto $scheme;
    }
}

# ---------- dify ----------
server {
    listen 443 ssl http2;
    server_name dify.local;

    ssl_certificate     /srv/ssl/n8n.local+1.pem;   # 同じ PEM が使える
    ssl_certificate_key /srv/ssl/n8n.local+1-key.pem;

    location / {
        proxy_pass         http://127.0.0.1:8081;   # dify コンテナ
        proxy_http_version 1.1;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

# 80 番への HTTP はまとめて HTTPS へリダイレクト
server {
    listen 80;
    server_name n8n.local dify.local;
    return 301 https://$host$request_uri;
}

```

有効化して再起動：

```bash
sudo ln -s /etc/nginx/sites-available/apps.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl restart nginx
```

## mDNS（.local 名前解決）設定

n8n.localとdify.localを同一LAN内で名前解決できるように設定します。
```bash
sudo apt install -y avahi-utils
```

hostのIPアドレスを動的に取得して、localと紐づけるシェルを作ります。

`/usr/local/bin/avahi-aliases.sh`を作成:

```sh:/usr/local/avahi-aliases.sh
IP=$(hostname -I | awk '{print $1}')
/usr/bin/avahi-publish -a -R n8n.local  "$IP" &
/usr/bin/avahi-publish -a -R dify.local "$IP" &
```

実行権限を付与します。

```bash
sudo chmod +x /usr/local/bin/avahi-aliases.sh
```

サービスからこのシェルを実行するようにします。

`/etc/systemd/system/avahi-aliases.service`を作成:

```:/etc/systemd/system/avahi-aliases.service
[Unit]
Description=Publish mDNS aliases for n8n & dify
After=avahi-daemon.service network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/avahi-aliases.sh
[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now avahi-aliases.service
```

## クライアント（mac）側でCA信頼

前節までの手順でhttpsアクセスが可能になりますが、ブラウザの警告が出てしまいます。
これを消すための設定をします。

ホストで発行したrootCA.pemをクライアントに登録します。
クライアントがmacなのでキーチェーンアクセスを使って登録します。

ホストOSで`mkcert -CAROOT`を実行し`rootCA.pem`（公開鍵）の場所を確認します。
ubuntuの場合、`~/.local/share/mkcert`などにあると思います。

場所がわかったらrootCA.pemをscpなどでクライアント側にコピーします。

コピーできたら、クライアント側でキーチェーンアクセスを起動し、システムの証明書一覧にrootCA.pemをドラッグ＆ドロップします。
するとmkcertで始まる新規の証明書が一覧に追加されるのでダブルクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259353/2ac27b77-4273-4031-95e0-106ec3082973.png)


ダブルクリックで表示された画面で証明書を「常に信頼」に設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259353/cad9be18-76fb-40bd-b558-8eea96eee5f1.png)

最後にブラウザを再起動します。

## 動作確認

1. `https://n8n.local` → n8n ダッシュボードが表示される
2. `https://dify.local` → dify が表示される
3. Mac のブラウザで証明書警告なし（鍵アイコン緑）