# nginx reverse proxy for crates-docs: TLS termination + X-API-Key -> Bearer.
#
# What it does:
# - Terminates HTTPS on :443 (bring your own certificate).
# - Rejects requests with no `X-API-Key` (except /health) with 401 at the edge.
# - Rewrites `X-API-Key: <k>` into `Authorization: Bearer <k>` so the
# crates-docs in-process auth layer enforces the key cryptographically.
# - Forwards to the loopback-only backend on 127.0.0.1:8080, with the
# buffering/timeout settings SSE needs.
#
# This is a server{} + the map{} it depends on; drop both into the http{} block
# of your nginx.conf (map MUST live at http scope, not inside server{}).
# Translate the incoming X-API-Key header into a bearer token. When the header
# is absent the variable is empty, which we reject per-location below.
map $http_x_api_key $bearer_token {
default "";
"~.+" "Bearer $http_x_api_key";
}
server {
listen 443 ssl;
http2 on; # nginx >= 1.25.1
# Older nginx (< 1.25.1): delete the line above and use instead:
# listen 443 ssl http2;
server_name docs.example.com;
ssl_certificate /etc/ssl/certs/crates-docs.crt;
ssl_certificate_key /etc/ssl/private/crates-docs.key;
ssl_protocols TLSv1.2 TLSv1.3;
# Health check stays open (no key required) for monitoring.
location = /health {
proxy_pass http://127.0.0.1:8080;
}
# MCP endpoints: /mcp (Streamable HTTP), /sse + /messages (SSE).
location / {
# Reject when X-API-Key is missing/empty before touching the backend.
if ($http_x_api_key = "") {
return 401;
}
# X-API-Key -> Authorization: Bearer for the in-process check.
proxy_set_header Authorization $bearer_token;
# Don't leak the raw key header upstream once translated.
proxy_set_header X-Api-Key "";
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;
# SSE / streaming: keep connections open and unbuffered so events flush
# immediately. These are required for the /sse transport to work.
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_pass http://127.0.0.1:8080;
}
}