Table of Contents

BookWyrm with Cloudflare Tunnel

This guide covers running BookWyrm using the official docker-compose from the BookWyrm repository, accessed via a Cloudflare Tunnel. The official compose already includes nginx and certbot. For a Cloudflare Tunnel setup, certbot is unnecessary since Cloudflare handles TLS at the edge.

See BookWyrm with Caddy if you are integrating BookWyrm into your own existing compose stack instead.

Traffic flow

``` Cloudflare – HTTPS –> cloudflared – HTTP –> nginx:80 –> /static/ served directly

  1. → everything else –> web:8000 (Gunicorn)

```

The most common mistake is pointing the tunnel directly at web:8000 (Gunicorn), which bypasses nginx entirely. Gunicorn returns HTML for every /static/ request, and the browser blocks it due to MIME type mismatch. No styles load.

Setup

1. Clone the BookWyrm repo

```bash git clone https://github.com/bookwyrm-social/bookwyrm.git cd bookwyrm git checkout production cp .env.example .env ```

Edit .env and fill in your domain, passwords, and email settings.

2. Switch nginx to reverse proxy mode

In your .env, add:

``` NGINX_SETUP=reverse_proxy ```

The BookWyrm nginx/ directory includes a reverse_proxy.conf specifically for setups where TLS is handled upstream. This makes nginx listen on port 80 only, with no SSL or certbot involvement.

Do not use NGINX_SETUP=http. There is no http.conf in the repo. If Docker cannot find the config file when the container starts, it creates a directory at that path instead, and nginx falls back to its default welcome page.

3. Set USE_HTTPS to true

Even though the server speaks plain HTTP internally, BookWyrm needs to know it is behind HTTPS so it generates correct https: URLs: ``` USE_HTTPS=true ``` ==== 4. Point your Cloudflare tunnel at nginx ==== In your cloudflared config (config.yml): ```yaml ingress: - hostname: books.yourdomain.com service: http://nginx:80 - service: http_status:404 ``` If you manage your tunnel through the Cloudflare dashboard, go to Zero Trust → Networks → Tunnels, edit your tunnel, and set the service URL to http://nginx:80. If cloudflared is running on the host rather than inside Docker, use http://localhost:<exposed-port> instead. ==== 5. Start the stack ==== Always use a full down followed by up when changing config. If you only run up -d, Docker may keep stale container mounts from a previous run: ```bash docker compose down && docker compose up -d ``` ===== Anubis (bot protection) ===== The official BookWyrm compose includes Anubis, a proof-of-work bot protection layer. nginx routes all requests through Anubis before passing them to Gunicorn. If you comment out Anubis, you must also fix the nginx/locations file. The default locations file has auth_request and proxy_pass inside location / tied to Anubis. Commenting out only Anubis leaves an empty or broken location / block, causing 502 on every request. ==== Running without Anubis ==== Replace the contents of nginx/locations with a version that removes all Anubis references: ```nginx http2 on; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; gzip on; gzip_disable “msie6”; proxy_read_timeout 1800s; chunked_transfer_encoding on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Host $host; proxy_redirect off; absolute_redirect off; location ~ ^/(login[^-/]|password-reset|resend-link|2fa-check) { limit_req zone=loginlimit burst=1 nodelay; limit_req_status 429; proxy_pass http://web; } location /api/updates/ { access_log off; proxy_pass http://web; } location / { proxy_pass http://web; } location /static/ { root /app; try_files $uri =404; add_header X-Cache-Status STATIC; access_log off; } location /images/ { location ~ \.(bmp|ico|jpg|jpeg|png|svg|tif|tiff|webp)$ { root /app; try_files $uri =404; add_header X-Cache-Status STATIC; access_log off; } return 403; } location /flower/ { proxy_pass http://flower; proxy_cache off; } ``` The file is called locations with no extension, inside the nginx/ directory next to docker-compose.yml. ===== Troubleshooting ===== ==== nginx shows its default welcome page ==== The NGINX_SETUP value does not match a file in nginx/. Check that nginx/reverse_proxy.conf exists. Then run docker compose down && docker compose up -d (not just up -d) to clear any bad bind mounts Docker created. ==== 502 Bad Gateway ==== nginx is running but cannot reach the upstream. The most likely cause is a broken nginx/locations file after commenting out Anubis. Check that location / has an active proxy_pass http://web; line (not commented out). Then restart: ```bash docker compose down && docker compose up -d ``` ==== CSS and JS still not loading after pointing tunnel at nginx ==== Confirm the tunnel is actually reaching nginx and not bypassing it. Check the tunnel service URL in your Cloudflare dashboard or config.yml and verify it says http://nginx:80, not http://web:8000''. ===== See Also ===== * BookWyrm overview * BookWyrm with Caddy Last updated: 2026-06-21 * Return to Docker