Reverse proxy & HTTPS
Put nginx in front of Tether to terminate HTTPS, compress responses, and handle the wildcard subdomain routing needed for client portals. This is required for any production deployment.
Login tokens and asset data travel between browser and server in plaintext over HTTP. Any network observer can steal session tokens. Always use HTTPS.
How the proxy works
nginx listens on ports 80 and 443. All HTTPS traffic is forwarded to Tether on port 8000. The critical requirement is that nginx passes the original Host header — Tether reads this to determine which tenant to serve. Without it, subdomain routing breaks and all requests land on the root domain.
Install nginx
bashsudo apt install -y nginx
nginx configuration
Create /etc/nginx/sites-available/tether:
nginx# Redirect HTTP to HTTPS server {{ listen 80; server_name atechsolutions.org *.atechsolutions.org; return 301 https://$host$request_uri; }} # Main HTTPS server server {{ listen 443 ssl http2; server_name atechsolutions.org *.atechsolutions.org; ssl_certificate /etc/letsencrypt/live/atechsolutions.org/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/atechsolutions.org/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; # IMPORTANT: Pass the real Host header — Tether uses this for tenant routing # Without this, ALL subdomains land on the wrong tenant or fail entirely 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; # Increase for large CSV imports client_max_body_size 50M; # Proxy to Tether location / {{ proxy_pass http://127.0.0.1:8000; proxy_read_timeout 60s; proxy_http_version 1.1; }} # Cache static assets at nginx (Chart.js, CSS, icons) location /static/ {{ proxy_pass http://127.0.0.1:8000; add_header Cache-Control "public, max-age=3600"; }} }}
bash# Enable the site sudo ln -s /etc/nginx/sites-available/tether /etc/nginx/sites-enabled/ # Test the config sudo nginx -t # Reload nginx sudo systemctl reload nginx
Wildcard SSL certificate
A wildcard certificate covers *.atechsolutions.org so every client subdomain
gets HTTPS automatically without any additional certificate work.
Wildcard certs require a DNS-01 challenge — you can't use the standard
HTTP-01 challenge because a wildcard can't be verified by serving a file.
With Cloudflare DNS (recommended)
bash# Install Certbot and the Cloudflare plugin sudo apt install -y certbot pip install certbot-dns-cloudflare # Create a Cloudflare API token with "Zone:DNS:Edit" permission for your domain # Then store it in a credentials file mkdir -p ~/.secrets cat > ~/.secrets/cloudflare.ini << 'CFEOF' dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN CFEOF chmod 600 ~/.secrets/cloudflare.ini # Issue the wildcard certificate sudo certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials ~/.secrets/cloudflare.ini \ -d atechsolutions.org \ -d "*.atechsolutions.org" \ --email admin@atechsolutions.org \ --agree-tos
Certbot installs a systemd timer that renews certificates before they expire. Check with: systemctl status certbot.timer
With other DNS providers
Certbot has plugins for Route53 (AWS), DigitalOcean, GoDaddy, and many others. See the full list at certbot.readthedocs.io. If your provider has no plugin, you can do a manual DNS-01 challenge (requires manual renewal every 90 days).
Verify
bash# Test the root domain curl -I https://atechsolutions.org # Should return: HTTP/2 200 # Test a client subdomain curl -I https://acme.atechsolutions.org # Should return: HTTP/2 200 # Verify the certificate covers both curl -vI https://acme.atechsolutions.org 2>&1 | grep "subject:"
Common proxy issues
All subdomains show the wrong tenant or MSP root
This means the Host header is not being passed through correctly.
Verify your nginx config has exactly:
nginxproxy_set_header Host $host;
Then reload nginx (sudo systemctl reload nginx) and test again.
Certificate file not found on nginx start
The certificate paths in nginx config must match exactly what Certbot created:
bash# Find where Certbot stored the cert sudo certbot certificates # Look for "Certificate Path:" and "Private Key Path:"
502 Bad Gateway
Tether is not running or not reachable on port 8000. Check:
bash# Is Tether running? docker compose ps # or for bare-metal: systemctl status tether # Can nginx reach it? curl http://127.0.0.1:8000/