Reverse proxy (nginx, HTTPS)¶
MAT runs as a single app (frontend + API) and does not configure your reverse proxy. This page is a community guide for putting MAT behind nginx with HTTPS. Use it as a starting point and adapt to your environment.
Overview¶
- Users access https://your-domain.com
- nginx terminates SSL and proxies to MAT (e.g. Docker on
localhost:3069or127.0.0.1:3069) - MAT receives HTTP from nginx; it relies on
X-Forwarded-ProtoandHostto know the public URL
nginx example¶
HTTP → HTTPS redirect¶
MAT behind HTTPS¶
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name domain.com;
# Frontend + API (single app)
location / {
proxy_pass http://127.0.0.1:3069;
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;
}
# SSL (adjust paths to your setup)
ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
Important: X-Forwarded-Proto must be set to $scheme (so https when users connect via HTTPS). Without it, MAT and OAuth flows can misbehave (redirect loops, cookie issues).
Environment: FRONTEND_BASE_URL¶
Set FRONTEND_BASE_URL in your .env to the external URL users use:
- HTTPS site:
FRONTEND_BASE_URL=https://domain.com(no trailing slash) - Local/dev:
FRONTEND_BASE_URL=http://localhost:3069
MAT uses this for:
- OAuth redirect URIs (Steam, Discord, etc.)
- Cookie configuration (secure cookies when the URL is
https://) - Session cookie domain — when the host is not
localhost, the admin session cookie (connect.sid) is explicitly set for this domain. This fixes “I’m admin but I don’t get an admin session” when running behind Cloudflare Tunnel or another proxy that forwards to an internal host: the app may see an internalHost, but the browser only talks to your public URL, so the cookie must be for that public host. - Direct-access block — when
FRONTEND_BASE_URLis a domain (not IP or localhost), MAT blocks admin UI/API for requests that look direct (noX-Forwarded-For/X-Forwarded-Proto). If you use a reverse proxy, it sends those headers, so admin works (including the cookie fallback when the session is dropped). If you use only IP or localhost asFRONTEND_BASE_URL, this block is not applied. - Links in emails or redirects
“Only works with HTTP” behind HTTPS¶
Some admins report MAT “only works” when they set FRONTEND_BASE_URL=http://... even though they serve the site over HTTPS. That usually means:
- The reverse proxy is not sending
X-Forwarded-Proto: https(and optionallyHost). - MAT or the OAuth provider then assume HTTP, which can cause redirect or cookie issues.
Fix:
- Add (or fix) these headers in your nginx
location /block: proxy_set_header X-Forwarded-Proto $scheme;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;- Use https in
.env:FRONTEND_BASE_URL=https://domain.com - Restart MAT and nginx.
After that, MAT should work correctly over HTTPS.
Cloudflare Tunnel¶
When you expose MAT via Cloudflare Tunnel (cloudflared), traffic flows: user → Cloudflare → tunnel → your MAT instance (e.g. Docker). The app often sees an internal Host (e.g. from Caddy or the container). If the session cookie were bound to that host, the browser (which only ever sees your public URL) would not send it, so you’d be logged in as a player (/me works) but admin routes would return 401 (“Missing or invalid admin session”).
Fix:
- Set
FRONTEND_BASE_URLto the public URL you use in the browser (e.g.https://cs.sivert.io). No trailing slash. - Ensure the tunnel forwards the original
Host(and ideallyX-Forwarded-Proto) to MAT. Many default setups already do. - Cloudflare Tunnel "HTTP Host Header": Leave this unset (Null). The tunnel will then forward the real
Host(e.g.cs.sivert.io). If you override it to an internal hostname or IP, MAT may set cookies for that host and the browser won’t send them when visiting your public URL. - Restart MAT.
MAT sets the session cookie domain from FRONTEND_BASE_URL when the host is not localhost, enables trust proxy, and uses a 200 + HTML meta-refresh redirect (instead of 302) after Steam login. Some browsers drop Set-Cookie on 302 responses when the request is a cross-site redirect (e.g. Steam → your domain); the 200 response avoids that, so the admin session is stored correctly behind the tunnel.
Direct container access¶
If you expose the MAT container port (e.g. 3069) directly (no reverse proxy), users may connect via http://<host-ip>:3069 or http://localhost:3069.
- When
FRONTEND_BASE_URLis a domain (e.g.https://domain.com): MAT treats requests withoutX-Forwarded-For/X-Forwarded-Protoas “direct” and blocks admin (403,authenticated: false). Use the reverse proxy URL for admin; ensure the proxy sends those headers. - When
FRONTEND_BASE_URLis IP or localhost (e.g.http://192.168.1.1:3069orhttp://localhost:3069): No direct-access block. Admin works via session or the cookie fallback (e.g. when Chrome drops the session cookie on 302). Suitable for setups without a reverse proxy.
Webhook URL and API URL¶
- Webhook URL (Settings in MAT, or
API_BASE_URL): This is where game servers (MatchZy) send events. It must be reachable from your CS2 hosts (often a public URL or LAN IP), e.g.https://domain.com/api/eventsorhttp://your-mat-ip:3069/api/events. - FRONTEND_BASE_URL: How users access the web UI (for OAuth redirects, etc.). Can be the same as the webhook base (e.g.
https://domain.com) if everything is behind the same nginx.
See Admin Settings and Server Setup for details.
Summary¶
| Item | What to do |
|---|---|
| nginx | Proxy / to MAT (e.g. http://127.0.0.1:3069), set Host, X-Forwarded-Proto, X-Forwarded-For |
FRONTEND_BASE_URL |
Use the public URL users see: https://domain.com when behind HTTPS |
| Webhook / API URL | Use a URL your CS2 servers can reach (often same as FRONTEND_BASE_URL or https://domain.com) |
If you use Caddy, Traefik, Cloudflare Tunnel, or another proxy, apply the same ideas: terminate TLS at the proxy (or at Cloudflare), forward to MAT over HTTP, and send X-Forwarded-Proto (and Host) so MAT knows the public scheme and host. Always set FRONTEND_BASE_URL to the URL users see in the browser.