Skip to content

Reverse Proxy Setup

Crit Alarm should always run behind a reverse proxy that terminates TLS (HTTPS).

Because Crit Alarm relies on authentication headers and custom ntfy headers (X-Priority, X-Title, X-Tags, etc.), your reverse proxy must pass all headers through intact.

  1. Authentication: If a proxy drops or strips the Authorization header, every webhook publish and management call fails with 401 Unauthorized.
  2. Priority Escalation: If X-Priority is stripped, alerts default to priority 3 (low/info). They will not open an incident or sound an emergency siren on your phone.
  3. Correct client IP: with X-Forwarded-For preserved, rate limiting counts the client, not your proxy.

Caddy automatically handles Let’s Encrypt TLS certificates and passes HTTP headers through by default.

alerts.example.com {
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}

Reload Caddy:

Terminal window
caddy reload

Traefik passes custom headers through to backend services automatically.

services:
critalarm:
image: ghcr.io/anvilnine/critalarm:latest
container_name: critalarm
restart: unless-stopped
environment:
- CRITALARM_BASE_URL=https://alerts.example.com
- CRITALARM_BEHIND_PROXY=true
labels:
- "traefik.enable=true"
- "traefik.http.routers.critalarm.rule=Host(`alerts.example.com`)"
- "traefik.http.routers.critalarm.entrypoints=websecure"
- "traefik.http.routers.critalarm.tls.certresolver=letsencrypt"
- "traefik.http.services.critalarm.loadbalancer.server.port=8080"
- "traefik.http.services.critalarm.loadbalancer.passHostHeader=true"
networks:
- traefik-net
networks:
traefik-net:
external: true

If using file-based dynamic configuration:

http:
routers:
critalarm:
rule: "Host(`alerts.example.com`)"
entryPoints:
- "websecure"
service: critalarm
tls:
certResolver: letsencrypt
services:
critalarm:
loadBalancer:
passHostHeader: true
servers:
- url: "http://127.0.0.1:8080"

Nginx needs explicit directives or it drops custom and authorization headers.

server {
listen 443 ssl http2;
server_name alerts.example.com;
ssl_certificate /etc/letsencrypt/live/alerts.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/alerts.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# Standard proxy headers
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;
proxy_set_header X-Forwarded-Host $host;
# Ensure all incoming request headers pass through
proxy_pass_request_headers on;
# Explicitly forward Authorization header
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
# Disable buffering for instantaneous webhook response
proxy_buffering off;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}

Check configuration and reload Nginx:

Terminal window
nginx -t && nginx -s reload

To test that your reverse proxy correctly forwards authentication and priority headers, execute a test publish using curl:

Terminal window
curl -v -X POST https://alerts.example.com/prod \
-H "Authorization: Bearer tk_YOUR_TOKEN" \
-H "X-Title: Reverse Proxy Verification" \
-H "X-Priority: 5" \
-d "Proxy test message"
  • HTTP 200 OK: Confirms the Authorization header passed through successfully. If you receive 401 Unauthorized, your proxy stripped the Authorization header.
  • "incident_id": "..." in JSON: Confirms the X-Priority: 5 header was received and triggered an incident. If no incident was opened, check whether custom X- headers were filtered.