Reverse proxy
This page helps you put Goiabada on the internet behind Nginx, with certificates from Let’s Encrypt and no Cloudflare.
Nginx answers HTTPS on ports 80 and 443 and forwards each request to the Docker Compose file the setup wizard writes, which publishes the two servers on 127.0.0.1 alone:
Browser ── HTTPS ──▶ Nginx on the host ── HTTP ──▶ auth.example.com → 127.0.0.1:9090 admin.example.com → 127.0.0.1:9091You need a server with Docker and Nginx, two hostnames under one domain, such as auth.example.com and admin.example.com, and the setup wizard. Both hostnames must share a registrable domain: see What every method needs.
Set it up
Section titled “Set it up”-
Point both hostnames at the server. At your DNS provider, add an
Arecord for each, with the server’s IP address:auth A <your-server-ip>admin A <your-server-ip>If the server has an IPv6 address too, add an
AAAArecord for each with it.dig +short auth.example.comshows the address once the record has spread. -
Generate and start Goiabada. Run the setup wizard, choose Production with reverse proxy, enter
https://auth.example.comandhttps://admin.example.com, then start the files it wrote, as Docker Compose describes:Terminal window docker compose up -dcurl http://127.0.0.1:9090/health # healthy -
Get the certificates. Install certbot and give Nginx a temporary site that answers Let’s Encrypt’s challenge for both hostnames:
Terminal window sudo apt-get update && sudo apt-get install certbotsudo mkdir -p /var/www/certbotWrite
/etc/nginx/sites-available/goiabada, with your own hostnames:server {listen 80;listen [::]:80;server_name auth.example.com admin.example.com;location /.well-known/acme-challenge/ {root /var/www/certbot;}}Enable it, and ask for one certificate per hostname:
Terminal window sudo ln -s /etc/nginx/sites-available/goiabada /etc/nginx/sites-enabled/sudo nginx -t && sudo nginx -s reloadsudo certbot certonly --webroot -w /var/www/certbot -d auth.example.comsudo certbot certonly --webroot -w /var/www/certbot -d admin.example.com -
Proxy both hostnames to Goiabada. Replace
/etc/nginx/sites-available/goiabadawith the full site:# Auth serverserver {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name auth.example.com;ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;ssl_protocols TLSv1.2 TLSv1.3;location / {proxy_pass http://127.0.0.1:9090;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;}location /.well-known/acme-challenge/ {root /var/www/certbot;}}# Admin consoleserver {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name admin.example.com;ssl_certificate /etc/letsencrypt/live/admin.example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;ssl_protocols TLSv1.2 TLSv1.3;location / {proxy_pass http://127.0.0.1:9091;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;}location /.well-known/acme-challenge/ {root /var/www/certbot;}}# HTTP: Let's Encrypt's challenge, and a redirect to HTTPS for everything elseserver {listen 80;listen [::]:80;server_name auth.example.com admin.example.com;location /.well-known/acme-challenge/ {root /var/www/certbot;}location / {return 301 https://$host$request_uri;}}http2 on;needs Nginx 1.25.1 or later, andnginx -vshows yours. On an older one, delete it and writelisten 443 ssl http2;andlisten [::]:443 ssl http2;instead.Terminal window sudo nginx -t && sudo systemctl reload nginx -
Open only 80 and 443, besides SSH. With ufw, allow your SSH port first, so turning the firewall on keeps your access.
Set
SSH_PORTto the port your SSH server listens on. In an SSH session, it’s the last field of$SSH_CONNECTION:Terminal window SSH_PORT="${SSH_CONNECTION##* }"; echo "SSH port: ${SSH_PORT:-unknown}"If that prints
unknown, or you’re in asudo -iorsushell, at a console, or in a tmux, screen or mosh session, which can keep an old value, look the port up and set it yourself, such asSSH_PORT=2222.sudo ss -tlnpshows SSH listening assshd, or assystemdon a host that starts it through socket activation, as recent Ubuntu releases do, wheresystemctl cat ssh.socketshows itsListenStream.Then add the rules. Nothing changes unless
SSH_PORTis a port from 1 to 65535 written without a leading zero, and each command runs only if the one before it succeeded. It runs the same in Bash and insh:Terminal window case "$SSH_PORT" in[1-9] | [1-9][0-9] | [1-9][0-9][0-9] | [1-9][0-9][0-9][0-9] | [1-9][0-9][0-9][0-9][0-9]) port_ok=yes ;;*) port_ok=no ;;esacif [ "$port_ok" = yes ] && [ "$SSH_PORT" -le 65535 ]; thensudo ufw allow "$SSH_PORT"/tcp && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw enableelseecho "SSH_PORT isn't a port from 1 to 65535; nothing was changed."fiBefore you close this session, open a second SSH connection to check that you still get in. ufw comes with Ubuntu; on Debian,
sudo apt-get install ufwfirst, or open the same ports in the firewall you use.Never open 9090 or 9091: a caller that reaches Goiabada around Nginx picks the IP address it’s rate limited and audited under.
-
Check the renewals. Let’s Encrypt certificates last 90 days, and the certbot package renews them on a timer:
Terminal window sudo certbot renew --dry-runsystemctl list-timers | grep certbot -
Sign in at
https://admin.example.com, as First sign-in describes.
What this setup gives you
Section titled “What this setup gives you”- Nginx terminates TLS. Goiabada serves plain HTTP on
127.0.0.1, which only processes on the host reach. Goiabada marks its cookiesSecurebecause its URLs arehttps://, whatever Nginx speaks to it. - Each server sees the client’s address, from the
X-Forwarded-Forentry Nginx appends: see Client IP and proxy trust. - The admin console reaches the auth server inside the Compose network, not through Nginx: see The hop to the auth server.
- Goiabada sets its own security headers,
Strict-Transport-Securityincluded once its URLs arehttps://, so Nginx adds none. A header Nginx added as well would reach the browser twice.