From d7c0d163f4fbc8270c5e2e38390da62986173a4e Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sat, 5 Apr 2025 18:21:05 +0300 Subject: [PATCH 1/9] Move nginx config to templates that will populate domain automatically in docker-compose run Include env defined nginx to nginx default.conf automatically Add nginx-variable to .env.example --- .env.example | 2 + docker-compose.yml | 6 ++- nginx/production | 120 ++++++++++++++++++++++---------------------- nginx/reverse_proxy | 2 +- nginx/ssl_bootstrap | 11 ++++ 5 files changed, 80 insertions(+), 61 deletions(-) create mode 100644 nginx/ssl_bootstrap diff --git a/.env.example b/.env.example index bd384ee78d..7a335b3cea 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,8 @@ USE_HTTPS=true DOMAIN=your.domain.here EMAIL=your@email.here +# docker compose NGINX setup: development, production, reverse_proxy +NGINX_SETUP=development # Instance default language (see options at bookwyrm/settings.py "LANGUAGES" LANGUAGE_CODE="en-us" diff --git a/docker-compose.yml b/docker-compose.yml index af0007e2cb..7b9d2c2f23 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,8 +8,12 @@ services: - web networks: - main + environment: + - DOMAIN=${DOMAIN} volumes: - - ./nginx:/etc/nginx/conf.d + - ./nginx/locations:/etc/nginx/conf.d/locations + - ./nginx/server_config:/etc/nginx/conf.d/server_config + - ./nginx/${NGINX_SETUP:-production}:/etc/nginx/templates/default.conf.template - static_volume:/app/static - media_volume:/app/images db: diff --git a/nginx/production b/nginx/production index fb71c7e2c0..a3bb72d633 100644 --- a/nginx/production +++ b/nginx/production @@ -11,72 +11,74 @@ server { listen [::]:80; listen 80; - server_name your-domain.com www.your-domain.com; + server_name ${DOMAIN} www.${DOMAIN}; location ~ /.well-known/acme-challenge { allow all; root /var/www/certbot; } -# # redirect http to https -# return 301 https://your-domain.com$request_uri; + # redirect http to https + return 301 https://${DOMAIN}$request_uri; } -# server { -# access_log /var/log/nginx/access.log cache_log; -# -# listen [::]:443 ssl http2; -# listen 443 ssl http2; -# -# server_name your-domain.com; -# -# client_max_body_size 3M; -# -# if ($host != "your-domain.com") { -# return 301 $scheme://your-domain.com$request_uri; -# } -# -# # SSL code -# ssl_certificate /etc/nginx/ssl/live/your-domain.com/fullchain.pem; -# ssl_certificate_key /etc/nginx/ssl/live/your-domain.com/privkey.pem; -# -# location ~ /.well-known/acme-challenge { -# allow all; -# root /var/www/certbot; -# } -# -# sendfile on; -# tcp_nopush on; -# tcp_nodelay on; -# keepalive_timeout 65; -# types_hash_max_size 2048; -# #include /etc/nginx/mime.types; -# #default_type application/octet-stream; -# -# gzip on; -# gzip_disable "msie6"; -# -# proxy_read_timeout 1800s; -# chunked_transfer_encoding on; -# -# # store responses to anonymous users for up to 1 minute -# proxy_cache bookwyrm_cache; -# proxy_cache_valid any 1m; -# add_header X-Cache-Status $upstream_cache_status; -# -# # ignore the set cookie header when deciding to -# # store a response in the cache -# proxy_ignore_headers Cache-Control Set-Cookie Expires; -# -# # PUT requests always bypass the cache -# # logged in sessions also do not populate the cache -# # to avoid serving personal data to anonymous users -# proxy_cache_methods GET HEAD; -# proxy_no_cache $cookie_sessionid; -# proxy_cache_bypass $cookie_sessionid; -# -# include /etc/nginx/conf.d/locations; -# -# } +server { + access_log /var/log/nginx/access.log cache_log; + + listen [::]:443 ssl; + listen 443 ssl; + + http2 on; + + server_name ${DOMAIN} www.${DOMAIN}; + + client_max_body_size 3M; + + if ($host != "${DOMAIN}") { + return 301 $scheme://${DOMAIN}$request_uri; + } + + # SSL code + ssl_certificate /etc/nginx/ssl/live/${DOMAIN}/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/live/${DOMAIN}/privkey.pem; + + location ~ /.well-known/acme-challenge { + allow all; + root /var/www/certbot; + } + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + #include /etc/nginx/mime.types; + #default_type application/octet-stream; + + gzip on; + gzip_disable "msie6"; + + proxy_read_timeout 1800s; + chunked_transfer_encoding on; + + # store responses to anonymous users for up to 1 minute + proxy_cache bookwyrm_cache; + proxy_cache_valid any 1m; + add_header X-Cache-Status $upstream_cache_status; + + # ignore the set cookie header when deciding to + # store a response in the cache + proxy_ignore_headers Cache-Control Set-Cookie Expires; + + # PUT requests always bypass the cache + # logged in sessions also do not populate the cache + # to avoid serving personal data to anonymous users + proxy_cache_methods GET HEAD; + proxy_no_cache $cookie_sessionid; + proxy_cache_bypass $cookie_sessionid; + + include /etc/nginx/conf.d/locations; + +} diff --git a/nginx/reverse_proxy b/nginx/reverse_proxy index cfae0692d7..7fffe42d13 100644 --- a/nginx/reverse_proxy +++ b/nginx/reverse_proxy @@ -13,7 +13,7 @@ server { listen [::]:8001; listen 8001; - server_name your-domain.com www.your-domain.com; + server_name ${DOMAIN} www.${DOMAIN}; include /etc/nginx/conf.d/locations; } diff --git a/nginx/ssl_bootstrap b/nginx/ssl_bootstrap new file mode 100644 index 0000000000..9322ac49d7 --- /dev/null +++ b/nginx/ssl_bootstrap @@ -0,0 +1,11 @@ +server { + listen [::]:80; + listen 80; + + server_name ${DOMAIN} www.${DOMAIN}; + + location ~ /.well-known/acme-challenge { + allow all; + root /var/www/certbot; + } +} From 428f8010ec1dfc62923b78945182c4634474755f Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sun, 6 Apr 2025 18:52:43 +0300 Subject: [PATCH 2/9] add bw-dev command to init letsencrypt certificate --- bw-dev | 3 +++ docker-compose-init_letsencrypt.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 docker-compose-init_letsencrypt.yml diff --git a/bw-dev b/bw-dev index f96474df13..7821570a98 100755 --- a/bw-dev +++ b/bw-dev @@ -93,6 +93,9 @@ case "$CMD" in initdb) initdb "$@" ;; + init_ssl) + $DOCKER_COMPOSE --file ./docker-compose-init_letsencrypt.yml up --exit-code-from=certbot + ;; resetdb) prod_error $DOCKER_COMPOSE rm -svf diff --git a/docker-compose-init_letsencrypt.yml b/docker-compose-init_letsencrypt.yml new file mode 100644 index 0000000000..6fb7f2305e --- /dev/null +++ b/docker-compose-init_letsencrypt.yml @@ -0,0 +1,27 @@ +services: + nginx: + image: nginx:1.25.2 + ports: + - "80:80" + - "443:443" + networks: + - main + environment: + - DOMAIN=${DOMAIN} + volumes: + - ./nginx/locations:/etc/nginx/conf.d/locations + - ./nginx/server_config:/etc/nginx/conf.d/server_config + - ./nginx/ssl_bootstrap:/etc/nginx/templates/default.conf.template + - ./certbot/conf:/etc/nginx/ssl + - ./certbot/data:/var/www/certbot + certbot: + image: certbot/certbot:latest + command: certonly --webroot --webroot-path=/var/www/certbot --keep-until-expiring --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN} -d www.${DOMAIN} + depends_on: + - nginx + volumes: + - ./certbot/conf:/etc/letsencrypt + - ./certbot/logs:/var/log/letsencrypt + - ./certbot/data:/var/www/certbot +networks: + main: From 1046c2ac36568bc76d64e04e27a9a5ffa4c86953 Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sun, 6 Apr 2025 18:53:20 +0300 Subject: [PATCH 3/9] change default docker-compose that is works with certbot if configured --- docker-compose.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 7b9d2c2f23..7c22747c41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,8 @@ services: image: nginx:1.25.2 restart: unless-stopped ports: - - "1333:80" + - "${PORT:-80}:80" + - "${PORT:-443:443}" depends_on: - web networks: @@ -14,6 +15,8 @@ services: - ./nginx/locations:/etc/nginx/conf.d/locations - ./nginx/server_config:/etc/nginx/conf.d/server_config - ./nginx/${NGINX_SETUP:-production}:/etc/nginx/templates/default.conf.template + - ./certbot/conf:/etc/nginx/ssl + - ./certbot/data:/var/www/certbot - static_volume:/app/static - media_volume:/app/images db: From 8f4eee3ce1db2544f5b8162c950d6889c78d560f Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Fri, 11 Apr 2025 19:33:24 +0300 Subject: [PATCH 4/9] nginx: split hostname to single-file that is included --- docker-compose-init_letsencrypt.yml | 1 + docker-compose.yml | 3 ++- nginx/production | 4 ++-- nginx/reverse_proxy | 2 +- nginx/server_name | 1 + nginx/ssl_bootstrap | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 nginx/server_name diff --git a/docker-compose-init_letsencrypt.yml b/docker-compose-init_letsencrypt.yml index 6fb7f2305e..21a04c67a3 100644 --- a/docker-compose-init_letsencrypt.yml +++ b/docker-compose-init_letsencrypt.yml @@ -11,6 +11,7 @@ services: volumes: - ./nginx/locations:/etc/nginx/conf.d/locations - ./nginx/server_config:/etc/nginx/conf.d/server_config + - ./nginx/server_name:/etc/nginx/conf.d/server_name - ./nginx/ssl_bootstrap:/etc/nginx/templates/default.conf.template - ./certbot/conf:/etc/nginx/ssl - ./certbot/data:/var/www/certbot diff --git a/docker-compose.yml b/docker-compose.yml index 7c22747c41..77f4cf0917 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,9 +12,10 @@ services: environment: - DOMAIN=${DOMAIN} volumes: + - ./nginx/${NGINX_SETUP:-production}:/etc/nginx/templates/default.conf.template - ./nginx/locations:/etc/nginx/conf.d/locations - ./nginx/server_config:/etc/nginx/conf.d/server_config - - ./nginx/${NGINX_SETUP:-production}:/etc/nginx/templates/default.conf.template + - ./nginx/server_name:/etc/nginx/conf.d/server_name - ./certbot/conf:/etc/nginx/ssl - ./certbot/data:/var/www/certbot - static_volume:/app/static diff --git a/nginx/production b/nginx/production index a3bb72d633..99d4766d63 100644 --- a/nginx/production +++ b/nginx/production @@ -11,7 +11,7 @@ server { listen [::]:80; listen 80; - server_name ${DOMAIN} www.${DOMAIN}; + include /etc/nginx/conf.d/server_name; location ~ /.well-known/acme-challenge { allow all; @@ -31,7 +31,7 @@ server { http2 on; - server_name ${DOMAIN} www.${DOMAIN}; + include /etc/nginx/conf.d/server_name; client_max_body_size 3M; diff --git a/nginx/reverse_proxy b/nginx/reverse_proxy index 7fffe42d13..b586ad8893 100644 --- a/nginx/reverse_proxy +++ b/nginx/reverse_proxy @@ -13,7 +13,7 @@ server { listen [::]:8001; listen 8001; - server_name ${DOMAIN} www.${DOMAIN}; + include /etc/nginx/conf.d/server_name; include /etc/nginx/conf.d/locations; } diff --git a/nginx/server_name b/nginx/server_name new file mode 100644 index 0000000000..201531562e --- /dev/null +++ b/nginx/server_name @@ -0,0 +1 @@ +server_name ${DOMAIN} www.${DOMAIN}; diff --git a/nginx/ssl_bootstrap b/nginx/ssl_bootstrap index 9322ac49d7..c94ca83878 100644 --- a/nginx/ssl_bootstrap +++ b/nginx/ssl_bootstrap @@ -2,7 +2,7 @@ server { listen [::]:80; listen 80; - server_name ${DOMAIN} www.${DOMAIN}; + include /etc/nginx/conf.d/server_name; location ~ /.well-known/acme-challenge { allow all; From c95cfa1869e71842b84ac3d73c886e0c48151f2d Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Mon, 21 Apr 2025 14:49:08 +0300 Subject: [PATCH 5/9] certbot: add automatical certbot loop in container and nginx to reload config periodically certbot will do renew once a day and nginx script will tell nginx to reload config once a day, so within 2 days when certificate is valid to be renewed, it is in use. --- docker-compose.yml | 12 ++++++++++++ nginx/99-autoreload.sh | 5 +++++ 2 files changed, 17 insertions(+) create mode 100755 nginx/99-autoreload.sh diff --git a/docker-compose.yml b/docker-compose.yml index 77f4cf0917..ce5fb230fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,10 +16,22 @@ services: - ./nginx/locations:/etc/nginx/conf.d/locations - ./nginx/server_config:/etc/nginx/conf.d/server_config - ./nginx/server_name:/etc/nginx/conf.d/server_name + - ./nginx/99-autoreload.sh:/docker-entrypoint.d/99-autoreload.sh - ./certbot/conf:/etc/nginx/ssl - ./certbot/data:/var/www/certbot - static_volume:/app/static - media_volume:/app/images + certbot: + image: certbot/certbot:latest + entrypoint: /bin/sh -c "trap exit TERM; while [ "$USE_HTTPS" = "true" ];do certbot renew --webroot --webroot-path=/var/www/certbot; sleep 1d & wait $${!}; done" + environment: + - USE_HTTPS=${USE_HTTPS} + depends_on: + - nginx + volumes: + - ./certbot/conf:/etc/letsencrypt + - ./certbot/logs:/var/log/letsencrypt + - ./certbot/data:/var/www/certbot db: image: postgres:13 env_file: .env diff --git a/nginx/99-autoreload.sh b/nginx/99-autoreload.sh new file mode 100755 index 0000000000..1977ab52bd --- /dev/null +++ b/nginx/99-autoreload.sh @@ -0,0 +1,5 @@ +#!/bin/bash +while true; do + sleep 1d + nginx -t && nginx -s reload +done & From 8bca77cfe4dba79fb10b2d8ea51f5a7ae1cfdbe6 Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sun, 27 Apr 2025 12:21:46 +0300 Subject: [PATCH 6/9] remove www-subdomain from nginx and certbot defaults --- docker-compose-init_letsencrypt.yml | 2 +- nginx/server_name | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-init_letsencrypt.yml b/docker-compose-init_letsencrypt.yml index 21a04c67a3..4bbe856c9e 100644 --- a/docker-compose-init_letsencrypt.yml +++ b/docker-compose-init_letsencrypt.yml @@ -17,7 +17,7 @@ services: - ./certbot/data:/var/www/certbot certbot: image: certbot/certbot:latest - command: certonly --webroot --webroot-path=/var/www/certbot --keep-until-expiring --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN} -d www.${DOMAIN} + command: certonly --webroot --webroot-path=/var/www/certbot --keep-until-expiring --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN} depends_on: - nginx volumes: diff --git a/nginx/server_name b/nginx/server_name index 201531562e..6b6da37399 100644 --- a/nginx/server_name +++ b/nginx/server_name @@ -1 +1 @@ -server_name ${DOMAIN} www.${DOMAIN}; +server_name ${DOMAIN}; From ba0f204f6a503fe5a3ad7bf17d1cbc738fc37289 Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sun, 27 Apr 2025 15:07:46 +0300 Subject: [PATCH 7/9] Rename nginx configs to https and reverse-proxy Development config was pretty much same as reverse-proxy and can be easily confusing which should be used. Now we make distinction just if you have something in front of nginx or does nginx handle the ssl traffic. If you use ssl certificates in docker-compose nginx, use https mode, if you have something in front of nginx, use revese-proxy mode --- .env.example | 18 +++++++++++++++--- docker-compose.yml | 2 +- nginx/{production => https.conf} | 0 nginx/reverse_proxy | 19 ------------------- nginx/{development => reverse_proxy.conf} | 3 +++ 5 files changed, 19 insertions(+), 23 deletions(-) rename nginx/{production => https.conf} (100%) delete mode 100644 nginx/reverse_proxy rename nginx/{development => reverse_proxy.conf} (95%) diff --git a/.env.example b/.env.example index 7a335b3cea..5cc9632be0 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,18 @@ USE_HTTPS=true DOMAIN=your.domain.here EMAIL=your@email.here -# docker compose NGINX setup: development, production, reverse_proxy -NGINX_SETUP=development +# If you use letsencrypt and get https directly to nginx, use https mode, +# if you have something in front of nginx, like traefik/ngrok/apache that handles cerfiticates and +# you want to pass just http to nginx, use reverse_proxy mode. Same for local development, you can +# use reverse_proxy config to allow testing/development without ssl certificate. +# +# if you define PORT, it is used as what port is mapped to http port. In case you need to redefine +# https port, you need to modify docker-compose.yml or add override file for port-mapping yourself. +# +# If this is not defined, we default to https mode +# +# docker compose NGINX setup: https, reverse_proxy +NGINX_SETUP=https # Instance default language (see options at bookwyrm/settings.py "LANGUAGES" LANGUAGE_CODE="en-us" @@ -20,7 +30,9 @@ DEFAULT_LANGUAGE="English" # Specify when the site is served from a port that is not the default # for the protocol (80 for HTTP or 443 for HTTPS). -# Probably only necessary in development. +# +# This tells what port to listen for example reverse_proxy mode, you shouldn't need to set it other +# cases. # PORT=1333 STATIC_ROOT=static/ diff --git a/docker-compose.yml b/docker-compose.yml index ce5fb230fa..d8aca7fc40 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: environment: - DOMAIN=${DOMAIN} volumes: - - ./nginx/${NGINX_SETUP:-production}:/etc/nginx/templates/default.conf.template + - ./nginx/${NGINX_SETUP:-https}.conf:/etc/nginx/templates/default.conf.template - ./nginx/locations:/etc/nginx/conf.d/locations - ./nginx/server_config:/etc/nginx/conf.d/server_config - ./nginx/server_name:/etc/nginx/conf.d/server_name diff --git a/nginx/production b/nginx/https.conf similarity index 100% rename from nginx/production rename to nginx/https.conf diff --git a/nginx/reverse_proxy b/nginx/reverse_proxy deleted file mode 100644 index b586ad8893..0000000000 --- a/nginx/reverse_proxy +++ /dev/null @@ -1,19 +0,0 @@ -include /etc/nginx/conf.d/server_config; - -upstream web { - server web:8000; -} - -upstream flower{ - server flower:8888; -} - -# Reverse-Proxy server -server { - listen [::]:8001; - listen 8001; - - include /etc/nginx/conf.d/server_name; - - include /etc/nginx/conf.d/locations; -} diff --git a/nginx/development b/nginx/reverse_proxy.conf similarity index 95% rename from nginx/development rename to nginx/reverse_proxy.conf index 26ef3a9353..57a1016213 100644 --- a/nginx/development +++ b/nginx/reverse_proxy.conf @@ -11,6 +11,9 @@ server { access_log /var/log/nginx/access.log cache_log; listen 80; + include /etc/nginx/conf.d/server_name; + + http2 on; sendfile on; tcp_nopush on; From 20603725b48bd670ff0bee39d26cfbb8dcc352fe Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sun, 27 Apr 2025 15:27:04 +0300 Subject: [PATCH 8/9] update certbot entrypoint to check nginx-config we can have revese-proxy with use-https enabled, but we just don't want letsencrypt in that case trying to update certificates --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d8aca7fc40..dd552f7dd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: - media_volume:/app/images certbot: image: certbot/certbot:latest - entrypoint: /bin/sh -c "trap exit TERM; while [ "$USE_HTTPS" = "true" ];do certbot renew --webroot --webroot-path=/var/www/certbot; sleep 1d & wait $${!}; done" + entrypoint: /bin/sh -c "trap exit TERM; while [ "$NGINX_SETUP" = "https" ];do certbot renew --webroot --webroot-path=/var/www/certbot; sleep 1d & wait $${!}; done" environment: - USE_HTTPS=${USE_HTTPS} depends_on: From ee08f8c81b6cc23709b319d861618720167fa48b Mon Sep 17 00:00:00 2001 From: Ilkka Ollakka Date: Sun, 27 Apr 2025 16:27:57 +0300 Subject: [PATCH 9/9] settings: check if we are behind reverse_proxy when setting netloc --- bookwyrm/settings.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 6da6f4bae0..dbc4fefc8f 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -361,7 +361,13 @@ CSRF_COOKIE_SECURE = True PORT = env.int("PORT", 443 if USE_HTTPS else 80) -if (USE_HTTPS and PORT == 443) or (not USE_HTTPS and PORT == 80): + +# If we are behind reverse_proxy, we can assume that protocol://domain should point to correct webserver that routes to our nginx +if ( + (USE_HTTPS and PORT == 443) + or (not USE_HTTPS and PORT == 80) + or (env("NGINX_SETUP", "https") == "reverse_proxy") +): NETLOC = DOMAIN else: NETLOC = f"{DOMAIN}:{PORT}"