Skip to content

Reverse proxy

GeoMetrikks works behind a TLS-terminating reverse proxy. Serve it on its own subdomain (for example geometrikks.example.com). The frontend is built for the site root, so subfolder setups (example.com/geometrikks/) do not work.

Recommended settings when proxied over HTTPS:

# The session cookie is only ever sent over HTTPS.
APP_SESSION_SECURE=true
# Trust X-Forwarded-For from your proxy so login logging records the real
# client IP. Use the narrowest range that covers the proxy. With Docker
# that is the network the proxy and GeoMetrikks share; docker network
# inspect <name> shows its subnet.
APP_TRUSTED_PROXIES=172.18.0.0/16

Docker picks that subnet when it creates the network, so a docker compose down followed by up can land on a different one and the login log goes back to showing the proxy’s container address. Pin it in your compose file if you do not want to chase it:

networks:
default:
ipam:
config:
- subnet: 172.18.0.0/16

With OpenID Connect, OIDC_REDIRECT_URI is the public https address of the app, and APP_SESSION_SECURE=true is required alongside it.

X-Forwarded-For is a plain header any client can send, so GeoMetrikks only honors it when the request arrives from an address listed in APP_TRUSTED_PROXIES; otherwise it uses the connection’s own address. Keep the range tight: everything inside it can put arbitrary addresses in the header.

The list has to cover every hop that adds to X-Forwarded-For before the request reaches the app, not just the last one, because the app reads the chain from the right and stops at the first address it does not trust. With Cloudflare connecting to Traefik directly (no Tunnel), that is the Docker network Traefik shares with the app plus Cloudflare’s published ranges (https://www.cloudflare.com/ips/), and Cloudflare’s ranges must also be on Traefik’s entrypoint (forwardedHeaders.trustedIPs, see docs/proxy-setup.md) or the visitor never enters the chain. nginx and SWAG with real_ip configured rewrite the peer at the proxy and hand the app a chain that already ends in the visitor, so there the Docker range alone is enough. A Cloudflare Tunnel is different: the peer is cloudflared, and the visitor arrives only in CF-Connecting-IP, which the app does not read. There the login log shows the cloudflared address unless nginx rewrites the peer with real_ip_header CF-Connecting-IP, as the Tunnel section of docs/proxy-setup.md shows.

APP_TRUSTED_PROXIES only affects the app’s own login logging; it has no effect on how the log parser reads your proxy’s access log files. For getting the real visitor address into those log files, see docs/proxy-setup.md.

The WebSocket feeds (/ws/live, /ws/crowdsec, /ws/logs) work through the standard Upgrade/Connection proxy headers, and idle connections survive nginx’s default proxy_read_timeout without extra tuning.

SWAG (linuxserver.io)

For linuxserver SWAG, drop this into /config/nginx/proxy-confs/geometrikks.subdomain.conf (the GeoMetrikks container must be named geometrikks and share a Docker network with SWAG):

## Version 2025/07/18
# make sure that your geometrikks container is named geometrikks
# make sure that your dns has a cname set for geometrikks
server {
listen 443 ssl;
# listen 443 quic;
listen [::]:443 ssl;
# listen [::]:443 quic;
server_name geometrikks.*;
include /config/nginx/ssl.conf;
client_max_body_size 0;
# enable for ldap auth (requires ldap-location.conf in the location block)
#include /config/nginx/ldap-server.conf;
# enable for Authelia (requires authelia-location.conf in the location block)
#include /config/nginx/authelia-server.conf;
# enable for Authentik (requires authentik-location.conf in the location block)
#include /config/nginx/authentik-server.conf;
# enable for Tinyauth (requires tinyauth-location.conf in the location block)
#include /config/nginx/tinyauth-server.conf;
location / {
# enable the next two lines for http auth
#auth_basic "Restricted";
#auth_basic_user_file /config/nginx/.htpasswd;
# enable for ldap auth (requires ldap-server.conf in the server block)
#include /config/nginx/ldap-location.conf;
# enable for Authelia (requires authelia-server.conf in the server block)
#include /config/nginx/authelia-location.conf;
# enable for Authentik (requires authentik-server.conf in the server block)
#include /config/nginx/authentik-location.conf;
# enable for Tinyauth (requires tinyauth-server.conf in the server block)
#include /config/nginx/tinyauth-location.conf;
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
set $upstream_app geometrikks;
set $upstream_port 8000;
set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
}
}

SWAG’s stock proxy.conf already sends the WebSocket upgrade and X-Forwarded-For headers. Set APP_TRUSTED_PROXIES to the Docker network SWAG shares with the app (for example 172.18.0.0/16).

Plain nginx

For a regular nginx install terminating TLS in front of the app:

# The Connection header must be "upgrade" for WebSocket handshakes and
# "close" otherwise; this map picks the right value per request.
map $http_upgrade $connection_upgrade {
default upgrade;
"" close;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name geometrikks.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
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;
# WebSocket upgrade for /ws/live, /ws/crowdsec and /ws/logs
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}

Adjust proxy_pass to wherever the app runs (container IP, another host). With nginx on the same machine as above, set APP_TRUSTED_PROXIES=127.0.0.1.

Notes for exposing GeoMetrikks to the internet:

  • /schema (the interactive API docs) and /health do not require login. If you don’t want them public, protect them at the proxy (an nginx location rule or your auth portal).
  • The login endpoint has no built-in rate limiting. Run fail2ban or CrowdSec against your proxy’s access logs to stop brute force at the edge.
  • Use APP_AUTH_DISABLED=true only when an authenticating proxy (Authelia, Tailscale, …) sits in front of the app.