🔒 Reverse Proxy & HTTPS Setup
This guide explains how to deploy MyAstroBoard behind a reverse proxy (like NGINX Proxy Manager, Traefik, or Apache) with HTTPS termination.
Overview
In a typical production deployment:
- Reverse Proxy (NGINX Proxy Manager, Traefik, etc.) handles HTTPS/SSL on port 443
- MyAstroBoard runs in Docker on HTTP (e.g., port 5000)
- The reverse proxy forwards requests to MyAstroBoard via HTTP internally
The Challenge
When the HTTPS-only session cookie setting is on, Flask sets the Secure flag on session cookies, which instructs browsers to only send the cookie over HTTPS connections. However, if Flask doesn’t know the original request was HTTPS (because it only sees HTTP from the proxy), cookies and sessions may not work correctly.
The Solution: ProxyFix Middleware
MyAstroBoard includes support for the ProxyFix middleware, which allows Flask to trust proxy headers sent by the reverse proxy. This is required for proper HTTPS operation.
Configuration
Step 1: Enable Proxy Settings in the Admin UI
After deploying, log in as admin and go to App settings -> Configuration -> Reverse proxy:
- ✅ Trust proxy headers (X-Forwarded-*) - required for Flask to see the real HTTPS scheme
- ✅ HTTPS-only session cookie (Secure flag) - prevents cookie transmission over HTTP
Click Save, then Restart container when prompted.
These settings are stored in the database (
data/myastroboard.db) and persist across container rebuilds.
Step 2: Configure Your Reverse Proxy
Your reverse proxy must send the following headers to MyAstroBoard:
Required Headers:
X-Forwarded-Proto: https(tells Flask the original request was HTTPS)X-Forwarded-For: <client-ip>(preserves client IP for logging)X-Forwarded-Host: <your-domain>(original hostname)X-Forwarded-Port: 443(original port)
Reverse Proxy Examples
NGINX Proxy Manager (NPM)
NPM Configuration
- Create Proxy Host in NGINX Proxy Manager:
- Domain Names:
myastroboard.yourdomain.com - Scheme:
http - Forward Hostname/IP: Your Docker host IP or container name
- Forward Port:
5000
- Domain Names:
- Cache Assets: ✅ Enabled (recommended)
- Block Common Exploits: ✅ Enabled
- Websockets Support: ✅ Enabled (if using any websocket features)
- SSL Tab:
- SSL Certificate: Select or create Let’s Encrypt certificate
- Force SSL: ✅ Enabled
- HTTP/2 Support: ✅ Enabled
- HSTS Enabled: ✅ Enabled (recommended)
- Advanced Tab (add this):
# Proxy headers for Flask ProxyFix
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Prefix /;
# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Timeouts (adjust if needed)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
Docker Compose for NPM + MyAstroBoard
If running NPM and MyAstroBoard on the same Docker host:
version: '3.8'
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
ports:
- "80:80"
- "443:443"
- "81:81" # NPM Admin UI
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
restart: unless-stopped
networks:
- proxy-network
myastroboard:
image: myastroboard/myastroboard:latest
container_name: myastroboard
environment:
- TZ=UTC
- DATA_DIR=/app/data
# Don't expose port 5000 externally - NPM will proxy to it
# After first login: App settings -> Configuration -> Reverse proxy -> enable both options -> Restart
expose:
- "5000"
volumes:
- ./myastroboard/data:/app/data
restart: unless-stopped
networks:
- proxy-network
networks:
proxy-network:
driver: bridge
In NPM, use myastroboard as the Forward Hostname (Docker service name) and 5000 as the port.
Traefik
docker-compose.yml with Traefik
version: '3.8'
services:
myastroboard:
image: myastroboard/myastroboard:latest
container_name: myastroboard
environment:
- TZ=UTC
- DATA_DIR=/app/data
# After first login: App settings -> Configuration -> Reverse proxy -> enable both options -> Restart
expose:
- "5000"
volumes:
- ./data:/app/data
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.myastroboard.rule=Host(`myastroboard.yourdomain.com`)"
- "traefik.http.routers.myastroboard.entrypoints=websecure"
- "traefik.http.routers.myastroboard.tls=true"
- "traefik.http.routers.myastroboard.tls.certresolver=letsencrypt"
- "traefik.http.services.myastroboard.loadbalancer.server.port=5000"
networks:
- traefik-network
networks:
traefik-network:
external: true
Traefik automatically adds the required X-Forwarded-* headers.
Standard NGINX
If using standalone NGINX (not NPM):
server {
listen 443 ssl http2;
server_name myastroboard.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:5000;
# Required headers for ProxyFix
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# Standard headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket support (if needed)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name myastroboard.yourdomain.com;
return 301 https://$server_name$request_uri;
}
Apache
<VirtualHost *:443>
ServerName myastroboard.yourdomain.com
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.pem
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
ProxyPass / http://localhost:5000/
ProxyPassReverse / http://localhost:5000/
</VirtualHost>
<VirtualHost *:80>
ServerName myastroboard.yourdomain.com
Redirect permanent / https://myastroboard.yourdomain.com/
</VirtualHost>
Serving under a sub-path
MyAstroBoard can also live under a path, e.g. https://example.org/myastroboard/. The proxy strips
the path before forwarding and sends it in X-Forwarded-Prefix; with Trust proxy headers
enabled, every page, API call and asset URL is then built under that prefix.
location /myastroboard/ {
proxy_pass http://myastroboard:5000/;
proxy_set_header X-Forwarded-Prefix /myastroboard;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Limitations under a sub-path: no installable app (PWA), no offline page and no notifications - the service worker is root-scoped (see NOTIFICATIONS.md). Serve MyAstroBoard from its own host name if you need them.
The Home Assistant app uses the same mechanism for its sidebar panel (ingress, via the
MYASTROBOARD_HA_INGRESS variable set by the app - never set it on a plain Docker install).
External base URL
Some addresses are opened by other software rather than by your browser: the Astrodex stream (Home Assistant camera, Lovelace card) and the address MyAstroShine sends photos back to. By default they are built from the address you are browsing on. Set App settings -> Configuration -> Reverse proxy -> External base URL when that address is not reachable by that software - it is required when you use MyAstroBoard from the Home Assistant sidebar.
Settings Reference
These used to be environment variables; since they moved to the admin UI, the variables
TRUST_PROXY_HEADERS, SESSION_COOKIE_SECURE and SECRET_KEY are ignored and can be removed from
docker-compose.yml.
| Setting (App settings -> Configuration -> Reverse proxy) | Default | Description |
|---|---|---|
| Trust proxy headers (X-Forwarded-*) | off | Enable the ProxyFix middleware so Flask sees the client’s address and the HTTPS scheme. Turn on only behind a trusted reverse proxy. Needs a restart |
| HTTPS-only session cookie | off | Set the Secure flag on the session cookie. Refused from a plain-HTTP page |
| External base URL | empty | Address other software reaches this instance at (see above) |
The session signing key is generated on first start and kept in the database: sessions survive
restarts and updates without any setting. The session cookie is always HttpOnly and
SameSite=Lax.
Testing Your Setup
1. Verify HTTPS is Working
curl -I https://myastroboard.yourdomain.com
Should return HTTP/2 200 or HTTP/1.1 200 OK
2. Check Secure Cookies
In browser DevTools (Network tab), inspect the response headers for /api/auth/login:
- Look for
Set-Cookieheader - Should include
Secure; HttpOnly; SameSite=Lax
3. Test Remember Me Feature
- Login with “Remember me” checked
- Close browser completely
- Reopen and navigate to your MyAstroBoard URL
- Should automatically be logged in
4. Check Logs
In MyAstroBoard logs:
docker logs myastroboard
You should see:
ProxyFix middleware enabled - trusting X-Forwarded-* headers from reverse proxy
Successful login for user admin (remember_me: True, permanent_session: True)
Session restored from cookie for user admin (permanent: True, endpoint: index)
Troubleshooting
❌ “Authentication required” after login with HTTPS
Cause: ProxyFix not enabled or proxy not sending X-Forwarded-Proto: https
Solution:
- Turn on Trust proxy headers in App settings -> Configuration -> Reverse proxy
- Verify proxy is sending
X-Forwarded-Proto: httpsheader - Restart MyAstroBoard (button in the same section, or restart the container)
❌ Cookies not persisting after browser restart
Cause: the HTTPS-only session cookie setting is on but the site is accessed via HTTP
Solution: Always access via HTTPS when that setting is on
❌ Cannot sign in, or sign out does nothing, over HTTP
Cause: the HTTPS-only session cookie setting (App settings -> Configuration -> Reverse proxy) is on,
and the page is opened over plain HTTP (for example http://192.168.1.10:5000 on the local
network). A browser neither stores nor deletes a Secure cookie on an HTTP page: a fresh sign-in
is lost at once, and a session that existed before the setting was turned on can no longer be
signed out.
Solution: turn the setting off from a page that still works: the browser that is still signed
in, the HTTPS address, or http://localhost (browsers accept Secure cookies there), for example
through an SSH tunnel: ssh -L 5000:localhost:5000 <host>. Since 1.8.2 the setting refuses to turn
on from a plain-HTTP page.
❌ Sessions invalidated after container restart
Cause: the data/ directory is not persisted (no volume mapped), so the database holding the
session key is recreated at each start.
Solution: map ./data:/app/data as in the provided docker-compose.yml.
❌ IP addresses in logs show proxy IP, not client IP
Cause: ProxyFix not enabled
Solution: Turn on Trust proxy headers (App settings -> Configuration -> Reverse proxy)
Security Best Practices
- ✅ Always use HTTPS in production with valid SSL certificates
- ✅ Enable Trust proxy headers only when behind a trusted reverse proxy
- ✅ Enable the HTTPS-only session cookie for HTTPS deployments
- ✅ Turn on two-factor authentication for accounts reachable from the internet (AUTHENTICATION.md)
- ✅ Keep Docker images updated (
docker compose pull) - ✅ Enable firewall rules to restrict direct access to port 5000
- ⚠️ Never expose port 5000 directly to the internet when using a reverse proxy
Quick Reference
| Deployment | Trust proxy headers | HTTPS-only session cookie |
|---|---|---|
| Local network, plain HTTP | off | off |
| Behind a reverse proxy with HTTPS | on | on |
Additional Resources
- NGINX Proxy Manager Documentation
- Traefik Documentation
- Werkzeug ProxyFix Documentation
- Flask Session Configuration
Last updated 2026-11-13