はじめに
前回の記事で、Docker版Jitsi Meetの、nftablesを使用したアクセス制限の設定方法をまとめました。
nftablesで、アクセス元を日本のIPアドレスに限定する設定も行いましたが、その場合、
「SSL証明書の自動更新ができなくなる」
(海外のサーバーからの証明書作成・更新時のドメイン認証アクセスができないため)
という問題があります。
また、Jitsi MeetのNginxアクセスログは、Dockerのログで確認できますが、アクセス元IPアドレスが、すべてDockerのブリッジインタフェースのアドレス 172.18.0.1 となるという問題もあります。
$ docker compose logs web web-1 | 172.18.0.1 - - [13/Jun/2025:10:34:52 +0900] "GET / HTTP/2.0" 200 9275 " -" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Ge cko) Chrome/141.0.0.0 Safari/537.36"
これではアクセス元を確認できず、トラブル時の調査もしにくいです。
これらの問題の解決方法として、ホストサーバー上にNginxをインストールし、リバースプロキシでリクエストをJitsi Meetのwebコンテナに転送するようにします。
また、これにより、以下のようなメリットもあります。
- アクセスログのローテートなどの管理が、コンテナのログ管理よりは容易となる。
- nftablesやNginxで、不正アクセスのブロック設定が可能となる。
- ホストで監視ツール(の管理画面)など、Jitsi Meet以外の何らかのWebサービスを追加、公開できる。
サーバー環境は、ここまでの記事で構築した Docker版Jitsi Meet on Ubuntu に、nftablesを使用したアクセス制限で、アクセス元を日本のIPアドレスに限定しているものとします。
Jitsi MeetのURLは https://meet.example.jp/ とします。
・Docker版Jitsi Meetのインストール手順
https://inaba-serverdesign.jp/blog/20250904/install-jitsi-meet-on-docker.html
・Jitsi Meet nftablesによるアクセス制限設定
https://inaba-serverdesign.jp/blog/20251007/jitsi-meet-restrict-by-nftables.html
構成イメージは以下のようになります。
・もともとの構成イメージ
・ホストサーバーのNginxリバースプロキシ設定後の構成イメージ
設定手順としては、まず、ホストサーバーのNginxリバースプロキシを設定します。
その後、SSL証明書をホストサーバーからLet’s Encryptで取得するよう変更します。
ホストサーバーのNginxリバースプロキシ設定
設定手順としては、Jitsi Meet公式ハンドブックの「Running behind a reverse proxy」の説明が参考になります。
・Self-Hosting Guide, Running behind a reverse proxy – Jitsi Meet Handbook
https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker/#running-behind-a-reverse-proxy
ホストサーバー上でNginxをインストールし、HTTP(S)ポートをリッスン、
リバースプロキシで、リクエストをホストサーバーのTCP/8000ポート経由でをJitsi Meetのwebコンテナに転送するようにします。
コンテナを停止
コンテナの設定を変更するため、いったん停止します。
$ cd ~/jitsi-meet/ $ docker compose stop [+] Stopping 4/4 ? Container jitsi-meet-jicofo-1 Stop... 6.0s ? Container jitsi-meet-web-1 Stopped 3.9s ? Container jitsi-meet-jvb-1 Stopped 4.1s ? Container jitsi-meet-prosody-1 Sto... 3.6s $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
※downで停止(削除)すると、Dockerネットワークが再作成となり、ブリッジインタフェース名が変更され、nftablesの修正が必要となります。ここでは stop で停止します。
nftalbesルールの追加
リバースプロキシで、リクエストをホストサーバーのTCP/8000ポート経由でをJitsi Meetのwebコンテナに転送するため、Dockerのユーザー(ここではJitsi Meet)定義ブリッジネットワークのインタフェースの TCP/8000 宛てのアクセス許可ルールを追加します。
# vim /etc/nftables.conf
...
define docker_user_bridge = {"br-xxxxxxxxxxxx"}
define docker_bridges = {"docker0", "br-xxxxxxxxxxxx"}
define docker_bridges_and_lo = {"docker0", "br-xxxxxxxxxxxx", "lo"}
...
table ip filter {
...
chain INPUT {
...
ct state new tcp dport 80 ip saddr @country_accept counter accept
ct state new tcp dport 443 ip saddr @country_accept counter accept
ct state new udp dport 10000 ip saddr @country_accept counter accept
iifname $docker_user_bridge ct state new tcp dport 8000 counter accept // この行を追加
ct state new tcp dport 22 ip saddr @country_accept counter accept
...
nftablesを reload して反映し、ルールセットを確認します。
# systemctl reload nftables
# nft list ruleset | less
table ip filter {
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.223.208.0/21, 223.223.224.0/19,
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 86 bytes 13951 acce
pt
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 4 bytes 380 accept
ct state new tcp dport 80 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
iifname "br-xxxxxxxxxxxx" ct state new tcp dport 8000 counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
...
Nginxのインストール、設定
Nginxパッケージをインストールします。
# apt update # apt install nginx Start-Date: 2025-06-13 11:03:14 Commandline: /usr/bin/apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold install nginx=1.24.0-2ubuntu7.3 Requested-By: ubuntu (1000) Install: nginx:amd64 (1.24.0-2ubuntu7.3), nginx-common:amd64 (1.24.0-2ubuntu7.3, automatic) End-Date: 2025-06-13 11:03:15
Nginxのグローバル設定はお好みで。
# cp -p /etc/nginx/nginx.conf /etc/nginx/nginx.conf.default
# vim /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 16384;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
#worker_connections 768;
worker_connections 2048;
# multi_accept on;
}
http {
##
# Basic Settings
##
#sendfile on;
#tcp_nopush on;
#types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
#ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 300;
types_hash_max_size 4096;
server_tokens off;
ignore_invalid_headers on;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
#include /etc/nginx/sites-enabled/*;
}
VirtualHost Configを作成します。
Jitsi Meet Handbookと、Jitsi Meet WebコンテナのConfig
~/.jitsi-meet-cfg/web/nginx/ssl.conf
を参考にしました。
HTTPは、HTTPSにリダイレクトし、常時SSL化します。
リクエストの転送先を 127.0.0.1:8000(ホストのTCP/8000ポート)とすることと、
URLパス /xmpp-websocket, /colibri-ws については、Webソケット通信を設定することがポイントです。
SSL証明書は、ひとまず、Jitsi Meetのデフォルト設定により acme.sh で取得済みのものを参照するようにします。
バインドマウントしているので、ホストサーバー上でアクセスできます。
# vim /etc/nginx/conf.d/vhost_meet.example.jp.conf
upstream backend {
server 127.0.0.1:8000;
}
server {
listen 443 ssl http2;
server_name meet.example.jp;
root /var/www/html;
index index.html index.htm;
access_log /var/log/nginx/ssl_meet.example.jp-access.log;
error_log /var/log/nginx/ssl_meet.example.jp-error.log;
# SSL
ssl_certificate /home/jitsi-meet/.jitsi-meet-cfg/web/acme-certs/meet.example.jp/fullchain.pem;
ssl_certificate_key /home/jitsi-meet/.jitsi-meet-cfg/web/acme-certs/meet.example.jp/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
# session settings
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# Proxy
location / {
proxy_pass http://backend;
# gzip
gzip on;
gzip_disable "MSIE [1-6]\.";
gzip_disable "Mozilla/4";
gzip_types text/plain
text/xml
text/css
application/xml
application/xhtml+xml
application/rss+xml
application/javascript
application/x-javascript;
gzip_buffers 4 8k;
gzip_min_length 1000;
gzip_comp_level 1;
gzip_proxied off;
gzip_vary off;
}
location /xmpp-websocket {
proxy_pass http://backend/xmpp-websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /colibri-ws {
proxy_pass http://backend/colibri-ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Access Log Off
location = /\. {deny all; access_log off; log_not_found off; }
location = /favicon.ico {access_log off; log_not_found off; }
location ~* /default\.ida {access_log off; log_not_found off; }
location ~* /cmd\.exe {access_log off; log_not_found off; }
location ~* /root\.exe {access_log off; log_not_found off; }
}
server {
listen 80;
server_name meet.example.jp;
root /var/www/html;
index index.html index.htm;
access_log /var/log/nginx/meet.example.jp-access.log;
error_log /var/log/nginx/meet.example.jp-error.log;
# redirect to HTTPS
location / {
return 301 https://meet.example.jp$request_uri;
}
# Access Log Off
location = /\. {deny all; access_log off; log_not_found off; }
location = /favicon.ico {access_log off; log_not_found off; }
location ~* /default\.ida {access_log off; log_not_found off; }
location ~* /cmd\.exe {access_log off; log_not_found off; }
location ~* /root\.exe {access_log off; log_not_found off; }
}
DHパラメータファイルを作成します。
# mkdir /etc/nginx/ssl # openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096 Generating DH parameters, 4096 bit long safe prime ...
Configのフォーマットをチェックし、Nginxを起動します。
(すでに起動してれば、restart で再起動します。)
※Jisti Meetコンテナが起動したままだと、Listenポート TCP/80,443 がバッティングして起動エラーとなります。
# nginx -t
# systemctl enable --now nginx
# systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: en>
Active: active (running) since Mon 2025-06-13 12:11:27 JST; 3s ago
Docs: man:nginx(8)
Process: 18291 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_proc>
Process: 18293 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (>
Main PID: 18294 (nginx)
Tasks: 7 (limit: 9428)
Memory: 8.8M (peak: 9.6M)
CPU: 34ms
CGroup: /system.slice/nginx.service
tq18294 "nginx: master process /usr/sbin/nginx -g daemon on; maste>
tq18295 "nginx: worker process"
tq18296 "nginx: worker process"
tq18297 "nginx: worker process"
tq18298 "nginx: worker process"
tq18299 "nginx: worker process"
mq18300 "nginx: worker process"
6月 13 12:11:27 meet.example.jp systemd[1]: Starting nginx.service - A high>
6月 13 12:11:27 meet.example.jp systemd[1]: Started nginx.service - A high
Jitsi Meetの設定変更
Jitsi Meetコンテナの環境設定ファイル .env を編集します。
$ cd jitsi-meet/ $ vim .env
ホストのHTTPポートは、先ほどのNginxのリバースプロキシ設定のとおり 8000 に変更します。
HTTPSポートは、Dockerコンテナでは使用しないので、HTTPS_PORT 行はコメントアウトします。
HTTP_PORT=8000 #HTTP_PORT=80 # Exposed HTTPS port #HTTPS_PORT=8443 #HTTPS_PORT=443
SSL証明書取得と、HTTPS、HTTPからのリダイレクトを無効とします。
ENABLE_LETSENCRYPT=0 DISABLE_HTTPS=1 ENABLE_HTTP_REDIRECT=0
続いて、docker-compose.yml で、webコンテナのポート転送の設定を行います。
ホストサーバーのlocalhost 127.0.0.1 の TCP/8000(.env で HTTP_PORT=8000 と設定済み)を、コンテナの TCP/80 にマッピングします。
コンテナではHTTPSは使用しないので、${HTTPS_PORT}:443 の行はコメントアウトします。
# vim docker-compose.yml
services:
# Frontend
web:
...
ports:
- '127.0.0.1:${HTTP_PORT}:80'
#- '${HTTPS_PORT}:443'
...
Dockerコンテナを起動します。
$ docker compose -f docker-compose.yml up -d
コンテナの起動とポート設定を確認します。
webコンテナは、127.0.0.1:8000->80/tcp となっていますね。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ac0e7885401d jitsi/web:stable-10184 "/init" 7 seconds ago Up 6 seconds 443/tcp, 127.0.0.1:8000->80/tcp jitsi-meet-web-1 de4925293082 jitsi/jicofo:stable-10184 "/init" 4 weeks ago Up 6 seconds 127.0.0.1:8888->8888/tcp jitsi-meet-jicofo-1 eadc1d0ad58e jitsi/prosody:stable-10184 "/init" 4 weeks ago Up 7 seconds 5222/tcp, 5269/tcp, 5280/tcp, 5347/tcp jitsi-meet-prosody-1 3e8fe5d89405 jitsi/jvb:stable-10184 "/init" 5 weeks ago Up 6 seconds 127.0.0.1:8080->8080/tcp, 0.0.0.0:10000->10000/udp, [::]:10000->10000/udp jitsi-meet-jvb-1
ホストサーバーのdocker-proxyプロセスの情報と、Listenポートを確認します。
docker-proxy では、ホスト 127.0.0.1 の TCP/8000 をコンテナ 172.18.0.5 の TCP/80 に転送していることがわかります。
TCP/80 と TCP/443 は、ホストサーバーのNginxがListenしているので、docker-proxyでは扱っていません。
# ps aux | grep docker-proxy
--
root 1999 0.0 0.4 1597364 4352 ? Sl 14:13 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8888 -container-ip 172.18.0.3 -container-port 8888 -use-listen-fd
root 2006 0.0 0.4 1597364 4352 ? Sl 14:13 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8080 -container-ip 172.18.0.4 -container-port 8080 -use-listen-fd
root 2010 0.0 0.4 1523632 4352 ? Sl 14:13 0:00 /usr/bin/docker-proxy -proto udp -host-ip 0.0.0.0 -host-port 10000 -container-ip 172.18.0.4 -container-port 10000 -use-listen-fd
root 2018 0.0 0.4 1597364 4352 ? Sl 14:13 0:00 /usr/bin/docker-proxy -proto udp -host-ip :: -host-port 10000 -container-ip 172.18.0.4 -container-port 10000 -use-listen-fd
root 2530 0.0 0.4 1523632 4224 ? Sl 14:13 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 8000 -container-ip 172.18.0.5 -container-port 80 -use-listen-fd
# ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=477,fd=15))
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=2006,fd=7))
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=477,fd=17))
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=774,fd=9),("nginx",pid=773,fd=9))
LISTEN 0 4096 127.0.0.1:8000 0.0.0.0:* users:(("docker-proxy",pid=2530,fd=7))
LISTEN 0 4096 127.0.0.1:8888 0.0.0.0:* users:(("docker-proxy",pid=1999,fd=7))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=774,fd=10),("nginx",pid=773,fd=10))
LISTEN 0 4096 *:22 *:* users:(("sshd",pid=765,fd=3),("systemd",pid=1,fd=90))
↑ネットワークポートの使用状況が、意図したとおりとなっていることが確認できました。
動作確認
Jitsi Meetの各機能について、ひととおりの動作確認を行います。
Nginxのアクセスログも確認します。
# less /var/log/nginx/ssl_meet.example.jp-access.log xxx.xxx.xxx.xxx - - [13/Jun/2025:14:21:09 +0900] "GET / HTTP/2.0" 200 9275 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" ...
↑アクセス元IPアドレスは、Dockerのブリッジインタフェースのアドレスではなく、意図したとおり、アクセスしたユーザーのIPアドレスとなっています。
SSL証明書をホストサーバーで取得するよう変更
SSL証明書は、Jitsi Meetのデフォルト設定により、webコンテナの acme.sh で ZeroSSL の証明書が設置されています。
これを、ホストサーバー上で、Let’s Encryptの証明書を取得、自動更新するよう変更します。
Certbotのインストール
Let’s Encryptのクライアントとしては、acme.sh や Lego などもありますが、ここでは、僕が一番慣れている Certbot をインストールします。
Certbotのインストール方法としては、snap版とpip版がありますが、snap版は systemd-timer を利用した自動更新機能もでインストールされ、更新日時を制御できません。
僕は更新日時を自分で決めたいので、今回はpip版をインストールすることにします。
(参考)
・certbot instruction
https://certbot.eff.org/instructions?ws=other&os=pip
Certbotが必要とするモジュールをインストールします。
# apt update # apt install python3 python3-venv libaugeas0 Start-Date: 2025-06-13 15:11:51 Commandline: /usr/bin/apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold install python3-venv=3.12.3-0ubuntu2 libaugeas0=1.14.1-1build2 Requested-By: ubuntu (1000) Install: python3-setuptools-whl:amd64 (68.1.2-2ubuntu1.2, automatic), python3-venv:amd64 (3.12.3-0ubuntu2), libaugeas0:amd64 (1.14.1-1build2), python3-pip-whl:amd64 (24.0+dfsg-1ubuntu1.1, automatic), python3.12-venv:amd64 (3.12.3-1ubuntu0.5, automatic), augeas-lenses:amd64 (1.14.1-1build2, automatic) End-Date: 2025-06-13 15:11:52
Pythonの仮想環境を作成します。
# python3 -m venv /opt/certbot/
# /opt/certbot/bin/pip install --upgrade pip
Requirement already satisfied: pip in /opt/certbot/lib/python3.12/site-packages (24.0)
Collecting pip
Downloading pip-25.1.1-py3-none-any.whl.metadata (3.7 kB)
Downloading pip-25.1.1-py3-none-any.whl (1.8 MB)
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq qq 1.8/1.8 MB 28.5 MB/s eta 0:00:00
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 25.1
Uninstalling pip-24.0:
Successfully uninstalled pip-24.0
Successfully installed pip-25.1.1
Certbotをインストールし、/usr/bin/certbot として使用できるようシンボリックリンクを作成します。
# /opt/certbot/bin/pip install certbot # ln -s /opt/certbot/bin/certbot /usr/bin/certbot
certbotコマンドのバージョン、ヘルプが実行できることを確認します。
# certbot --version
certbot 4.0.0
# certbot --help
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...
Certbot can obtain and install HTTPS/TLS/SSL certificates. By default,
it will attempt to use a webserver both for obtaining and installing the
certificate. The most common SUBCOMMANDS and flags are:
obtain, install, and renew certificates:
(default) run Obtain & install a certificate in your current webserver
certonly Obtain or renew a certificate, but do not install it
renew Renew all previously obtained certificates that are near
expiry
enhance Add security enhancements to your existing configuration
-d DOMAINS Comma-separated list of domains to obtain a certificate for
(the certbot apache plugin is not installed)
--standalone Run a standalone webserver for authentication
(the certbot nginx plugin is not installed)
--webroot Place files in a server's webroot folder for authentication
--manual Obtain certificates interactively, or using shell script
hooks
...
Let’s Encryptの認証のための準備
Let’s Encyprt認証サーバーからの認証アクセスURLは
https://meet.example.jp/.well-know/acme-challenge/~
となります。
このため、Nginxで、URLパス /.well-know/~ はコンテナにリバースプロキシせずに、
/var/www/certbot/meet.example.jp/.well-known/~
にアクセスさせるよう設定します。
まず、認証用ファイルを設置するディレクトリを作成します。
# mkdir -p /var/www/certbot/meet.example.jp
Nginx の VirtualHost Config で、Let’s Encrypt認証用のエイリアス設定を追加します。
(常時SSL化で、HTTPはHTTPSにリダイレクトしているため)
HTTPS用の Listen 443 のほうの server ディレクティブのProxy設定の前に追記します。
# vim /etc/nginx/conf.d/vhost_meet.example.jp.conf
# Let's Encrypt
location ^~ /.well-known/ {
root /var/www/certbot/meet.example.jp;
}
Nginxをreloadして反映します。
# nginx -t # systemctl reload nginx
nftablesの設定変更
※もともと、任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可している場合は、この設定は不要です。
certbotコマンドでLet’s EncryptのSSL証明書を取得しますが、
今回のサーバー環境では、nftablesでアクセス元を日本のIPアドレスに限定するよう設定済みのため、そのままでは、海外のLet’s Encryptからの認証アクセスがブロックされ、証明書を取得できません。
証明書取得・更新時に、nftablesで任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可する個別ルールを一時的に追加・削除してもよいのですが、nftablesの個別ルールにはナンバリング機能がないため、個別ルールの追加・削除制御は難しいです。
ここでは、「通常時のルールセット(/etc/nftables_normal.conf)」と、
任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可するルールを追加した、SSL証明書取得用の「certbot実行時のルールセット(/etc/nftables_certbot.conf)」
を用意し、切り替えるようにします。
また、nftablesサービスは、/etc/nftables.conf を参照するため、/etc/nftables.conf はシンボリックリンクとして、参照先の実ファイルを /etc/nftables_normal.conf と /etc/nftables_certbot.conf で切り替えます。
まず、通常時のルールセットを /etc/nftables_normal.conf に rename し、シンボリックリンク /etc/nftables.conf を作成します。
# mv /etc/nftables.conf /etc/nftables_normal.conf # ln -s /etc/nftables_normal.conf /etc/nftables.conf
念のため、nftables を reload して、正しく反映されることを確認します。
# systemctl reload nftables
# nft list ruleset | less
table ip filter {
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.223.208.0/21, 223.223.224.0/19,
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 5 bytes 380 accept
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 0 bytes 0 accept
ct state new tcp dport 80 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
iifname "br-xxxxxxxxxxxx" ct state new tcp dport 8000 counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
...
続いて、certbot実行時のルールセットを作成します。
通常時のルールセットをコピーしてから、任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可するルールを1行追加します。
# cp -p /etc/nftables_normal.conf /etc/nftables_certbot.conf
# vim /etc/nftables_certbot.conf
#!/usr/sbin/nft -f
flush ruleset
include "/etc/nftables_country_allowlist"
define docker_user_bridge = {"br-xxxxxxxxxxxx"}
define docker_bridges = {"docker0", "br-xxxxxxxxxxxx"}
define docker_bridges_and_lo = {"docker0", "br-xxxxxxxxxxxx", "lo"}
table ip filter {
set country_accept {
type ipv4_addr; flags interval;
elements = $country_allowlist
}
chain INPUT {
type filter hook input priority 0; policy drop;
ct state established,related counter accept
ct state invalid counter drop
iifname "lo" counter accept
ct state new tcp dport 80 ip saddr @country_accept counter accept
ct state new tcp dport 443 ip saddr @country_accept counter accept
ct state new udp dport 10000 ip saddr @country_accept counter accept
iifname $docker_user_bridge ct state new tcp dport 8000 counter accept
ct state new tcp dport 22 ip saddr @country_accept counter accept
ct state new tcp dport { 80, 443 } counter accept // この行追加
...
Let’s Encrypt証明書取得のため、certbot実行用のルールセットに切り替えます。
# ln -s -i /etc/nftables_certbot.conf /etc/nftables.conf # ls -l /etc/nftables* lrwxrwxrwx 1 root root 25 6月 13 16:45 /etc/nftables.conf -> /etc/nftables_certbot.conf -rwxr-xr-x 1 root root 243 4月 23 2024 /etc/nftables.conf.default -rwxr-xr-x 1 root root 1917 6月 13 16:05 /etc/nftables_certbot.conf -rw-r--r-- 1 root root 73005 6月 12 14:05 /etc/nftables_country_allowlist -rwxr-xr-x 1 root root 1862 6月 10 17:59 /etc/nftables_normal.conf
nftables を reload して反映し、任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可するルールが追加されたことを確認します。
# systemctl reload nftables
# nft list ruleset | less
table ip filter {
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.223.208.0/21, 223.223.224.0/19,
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 9 bytes 612 accept
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 0 bytes 0 accept
ct state new tcp dport 80 ip saddr @country_accept counter packe
ts 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
iifname "br-xxxxxxxxxxxx" ct state new tcp dport 8000 counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport { 80, 443 } counter packets 0 bytes 0 accept
...
SSL証明書を取得
certbotコマンドで、SSL証明書を取得します。
オプションについて補足します。
- –webroot で、Apacheを起動したままの認証とする。
- –preferred-challenges http で認証方式 http を指定。
- -dオプションで、ドメインを指定。
- -wオプションで、認証時のDocumentRootディレクトリを指定。
- –agree-tos オプションで、利用規約に同意し、同意の確認画面を表示しない。
- –register-unsafely-without-email オプションで、Let’s Encryptにメールアドレスを登録しない。(-mオプションで登録したメールアドレスへの有効期限通知は、2025年6月に廃止されました。)
# /usr/bin/certbot certonly \
--preferred-challenges http \
--webroot -w /var/www/certbot/meet.example.jp \
-d meet.example.jp \
--agree-tos \
--register-unsafely-without-email
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account registered.
Requesting a certificate for meet.example.jp
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/meet.example.jp/fullchain.pem
Key is saved at: /etc/letsencrypt/live/meet.example.jp/privkey.pem
This certificate expires on 2025-09-11.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
↑「Successfully received certificate.」となったので、正しく発行された。
証明書ファイル群を確認します。
# ls -l /etc/letsencrypt/live/meet.example.jp/ -rw-r--r-- 1 root root 692 6月 13 18:06 README lrwxrwxrwx 1 root root 42 6月 13 18:06 cert.pem -> ../../archive/meet.example.jp/cert1.pem lrwxrwxrwx 1 root root 43 6月 13 18:06 chain.pem -> ../../archive/meet.example.jp/chain1.pem lrwxrwxrwx 1 root root 47 6月 13 18:06 fullchain.pem -> ../../archive/meet.example.jp/fullchain1.pem lrwxrwxrwx 1 root root 45 6月 13 18:06 privkey.pem -> ../../archive/meet.example.jp/privkey1.pem
念のため、証明書ファイルの内容を確認します。
# openssl x509 -text -noout -in /etc/letsencrypt/live/meet.example.jp/cert.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
05:09:8f:b9:75:14:54:19:a3:d3:39:cc:97:78:dc:94:xx:xx
Signature Algorithm: ecdsa-with-SHA384
Issuer: C = US, O = Let's Encrypt, CN = E5
Validity
Not Before: Jun 13 08:06:28 2025 GMT
Not After : Sep 11 08:06:27 2025 GMT
Subject: CN = meet.example.jp
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
...
X509v3 Subject Alternative Name:
DNS:meet.example.jp
...
CN と DNS が、meet.example.jp
Not Before が当日、Not After が90日後となっているのでOKです。
証明書が正しく発行されたことを確認できたので、通常時のnftablesルールセットに切り替えます。
# ln -s -i /etc/nftables_normal.conf /etc/nftables.conf # ls -l /etc/nftables* lrwxrwxrwx 1 root root 25 6月 13 18:15 /etc/nftables.conf -> /etc/nftables_normal.conf -rwxr-xr-x 1 root root 243 4月 23 2024 /etc/nftables.conf.default -rwxr-xr-x 1 root root 1917 6月 13 16:05 /etc/nftables_certbot.conf -rw-r--r-- 1 root root 73005 6月 12 14:05 /etc/nftables_country_allowlist -rwxr-xr-x 1 root root 1862 6月 10 17:59 /etc/nftables_normal.conf
nftables を reload して反映し、任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可するルールが削除されたことを確認します。
# systemctl reload nftables
# nft list ruleset | less
table ip filter {
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.223.208.0/21, 223.223.224.0/19,
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 41 bytes 52081 acce
pt
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 1 bytes 60 accept
ct state new tcp dport 80 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
iifname "br-xxxxxxxxxxxx" ct state new tcp dport 8000 counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
icmp type echo-reply counter packets 0 bytes 0 accept
icmp type destination-unreachable counter packets 0 bytes 0 accept
icmp type time-exceeded counter packets 0 bytes 0 accept
}
...
日本のIPアドレスに限定する設定に戻したため、念のため、海外のIPアドレスからアクセスできないことを確認すると安心です。
NginxでSSL証明書の参照先を変更
Nginx の VirtualHost Configで、Nginxが参照する証明書ファイルを、先ほどLet’s Encryptで取得した証明書ファイルに変更します。
# vim /etc/nginx/conf.d/vhost_meet.example.jp.conf
...
# SSL
#ssl_certificate /home/jitsi-meet/.jitsi-meet-cfg/web/acme-certs/meet.example.jp/fullchain.pem;
#ssl_certificate_key /home/jitsi-meet/.jitsi-meet-cfg/web/acme-certs/meet.example.jp/key.pem;
ssl_certificate /etc/letsencrypt/live/meet.example.jp/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/meet.example.jp/privkey.pem;
...
Nginxをreloadして反映します。
# nginx -t # systemctl reload nginx
Webブラウザからアクセスして、証明書情報を確認し、Let’s Encryptの証明書に変わったことをします。
https://meet.example.jp/
Let’s Encrypt証明書の自動更新設定
Let’s Encrypt証明書は有効期限が90日間です。
snap版certbotをインストールしていれば、自動更新設定も自動的にインストールされますが、今回はpip版certbotをインストールしましたので、証明書の自動更新は自分で設定します。
ここでは、証明書を自動更新するシェルスクリプトを作成し、毎週月-金曜の12時15分に自動更新を試みるよう設定します。
certbotコマンドの実行前後に、nftablesのルールセットを切り替える(シンボリックリンクの参照先を変更)のがポイントです。
なお、certbotには、ランダムディレイ機能が含まれたので、今回のコマンドでは、ランダムディレイは含めず、コメントアウトします。
# vim /root/bin/update_sslcert.sh
#!/bin/bash
#
CERTBOT_CMD=/usr/bin/certbot
WEBSERVER_RESTART_CMD="systemctl reload nginx"
MAILTO=root
echo "===== Update SSL Certfile ====="
echo "`date` Update SSL Certfile start"
# 0-300秒のランダムディレイ
###/opt/certbot/bin/python -c 'import random; import time; time.sleep(random.random() * 300)'
# nftablesで一時的にHTTP/HTTPSを任意のIPアドレスに許可するルールセットに変更
ln -fs /etc/nftables_certbot.conf /etc/nftables.conf
systemctl reload nftables
# 証明書の更新
${CERTBOT_CMD} renew \
--post-hook "${WEBSERVER_RESTART_CMD}"
# nftablesでHTTP/HTTPSを限定する通常時ルールセットに戻す
ln -fs /etc/nftables_normal.conf /etc/nftables.conf
systemctl reload nftables
echo "`date` Update SSL Certfile end"
# EOF
↑nftablesルールセットの切り替えは、certbotコマンドの --pre-hook, --post-hook オプションで指定するほうがスマートかもしれませんね。
実行権限を付与します。
# chmod 700 /root/bin/update_sslcert.sh
ここで、証明書の更新テストのため、まだ有効期限が30日以上ある証明書を強制的に更新するため、certbotコマンドに --force-renewal オプションを追加します。
# vim /root/bin/update_sslcert.sh
...
# 証明書の更新
${CERTBOT_CMD} renew --force-renewal \
--post-hook "${WEBSERVER_RESTART_CMD}"
...
cronで、毎週月-金曜の12:15に実施するよう設定します。
また、テストのため、数分後に実施するようエントリーを追加します。
# crontab -e # Update SSL Cert 15 12 * * mon-fri /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1 20 18 * * * /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1
10分ほど待って、確認します。
まず、証明書ファイルが更新されたことを確認します。
# ls -l /etc/letsencrypt/live/*/ -rw-r--r-- 1 root root 692 6月 13 18:06 README lrwxrwxrwx 1 root root 42 6月 13 18:24 cert.pem -> ../../archive/meet.example.jp/cert2.pem lrwxrwxrwx 1 root root 43 6月 13 18:24 chain.pem -> ../../archive/meet.example.jp/chain2.pem lrwxrwxrwx 1 root root 47 6月 13 18:24 fullchain.pem -> ../../archive/meet.example.jp/fullchain2.pem lrwxrwxrwx 1 root root 45 6月 13 18:24 privkey.pem -> ../../archive/meet.example.jp/privkey2.pem
↑シンボリックリンクの参照先実ファイル名の世代部分が 1 から 2 に変わっています。
cronで指定したシェルスクリプトのログを確認します。
# less /var/log/update_sslcert.log ===== Update SSL Certfile ===== 2025年 6月 13日 金曜日 18:24:01 JST Update SSL Certfile start Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/meet.example.jp.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Renewing an existing certificate for meet.example.jp - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations, all renewals succeeded: /etc/letsencrypt/live/meet.example.jp/fullchain.pem (success) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2025年 6月 13日 月曜日 18:24:04 JST Update SSL Certfile end
nftablesのルールセットで、任意のアドレス(0.0.0.0)に対してHTTP, HTTPポートを許可するルールが削除されていることを確認します。
# nft list ruleset | less
table ip filter {
set country_accept {
type ipv4_addr
flags interval
elements = { 1.0.16.0/20, 1.0.64.0/18,
1.1.64.0/18, 1.5.0.0/16,
...
223.223.208.0/21, 223.223.224.0/19,
223.252.64.0/19, 223.252.112.0/20 }
}
chain INPUT {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 5 bytes 380 accept
ct state invalid counter packets 0 bytes 0 drop
iifname "lo" counter packets 0 bytes 0 accept
ct state new tcp dport 80 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new tcp dport 443 ip saddr @country_accept counter packets 0 bytes 0 accept
ct state new udp dport 10000 ip saddr @country_accept counter packets 0 bytes 0 accept
iifname "br-xxxxxxxxxxxx" ct state new tcp dport 8000 counter packets 0 bytes 0 accept
ct state new tcp dport 22 ip saddr @country_accept counter packets 0 bytes 0 accept
ブラウザからアクセスし、証明書の情報を表示して、更新されたことを確認します。
確認できたら、証明書を自動更新するシェルスクリプト内の、--force-renewal オプションを削除します。
# vim /root/bin/update_sslcert.sh
...
# 証明書の更新
${CERTBOT_CMD} renew \
--post-hook "${WEBSERVER_RESTART_CMD}"
...
テストのため、数分後に実施するよう追加したcronエントリーを削除します。
# crontab -e # Update SSL Cert 15 12 * * mon-fri /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1 20 18 * * * /root/bin/update_sslcert.sh 1>> /var/log/update_sslcert.log 2>&1 // この行削除
ログローテートの設定
# vim /etc/logrotate.d/update_sslcert
/var/log/update_sslcert.log
{
monthly
rotate 12
compress
missingok
}
Dockerコンテナの証明書更新を停止
Jitsi Meet webコンテナのSSL証明書は不要となったため、自動更新を停止します。
Jitsi Meetのwebコンテナ内で調査すると、rootユーザーのcronエントリーで自動更新を実施していることがわかります。
-- /var/spool/cron/crontabls/root # DO NOT EDIT THIS FILE - edit the master and reinstall. # (- installed on Wed Apr 2 19:21:15 2025) # (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $) 15 21 * * * "/config/acme.sh"/acme.sh --cron --home "/config/acme.sh" > /dev/null --
上位ディレクトリ crontabs はバインドマウントしているため、このファイルは、ホストサーバー側で編集できます。
(パーミッションにより、rootユーザーで編集する必要があります。)
# cd /home/jitsi-meet/.jitsi-meet-cfg/web/crontabs/ # ls -l -rw------- 1 root lxd 256 6月 9 12:16 root
acme.sh を実行するエントリーをコメントアウトします。
# vim root # DO NOT EDIT THIS FILE - edit the master and reinstall. # (- installed on Wed Apr 2 19:21:15 2025) # (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $) #15 21 * * * "/config/acme.sh"/acme.sh --cron --home "/config/acme.sh" > /dev/null
Dockerコンテナのシェルで、cronサービスを再起動して、cronエントリーの変更を反映します。
$ cd ~/jitsi-meet/ $ docker compose exec web /bin/bash # service cron restart Restarting periodic command scheduler: cronStopping periodic command scheduler: cron. Starting periodic command scheduler: cron.
おわりに
Jitsi Meetコンテナの運用に関するいくつかの問題の対策として、ホストサーバー上にNginxをインストールし、リバースプロキシでリクエストをJitsi Meetのwebコンテナに転送する設定と、それに関連するSSL証明書の自動更新まわりの設定について記載しました。
Jitsi Meetに限らず、コンテナアプリケーションを稼働させるケースでは、有益な方法だと思います。
Jitsi Meetについて書いた記事まとめ。

