Reverse Proxy Setup
ahdapa can run behind a TLS-terminating reverse proxy. The proxy handles HTTPS towards clients; ahdapa listens on a plain HTTP port reachable only from the proxy.
This page covers Apache httpd and nginx. Other proxies (HAProxy, Caddy) follow the same principles.
ahdapa configuration
Regardless of which proxy you use, configure ahdapa as follows:
Set issuer to the public HTTPS URL — it appears in the OIDC discovery
document and in JWT iss claims. Set listen to a loopback address or Unix
socket that the proxy can reach. Do not add a [tls] section; the proxy
terminates TLS so ahdapa does not need to.
[server]
issuer = "https://idp.example.com"
listen = "127.0.0.1:8080" # reachable only from the proxy
[db]
url = "sqlite:///var/lib/ahdapa/ahdapa.db"
# No [tls] section — TLS is terminated by the proxy.
Using a Unix domain socket instead of a TCP port is also supported:
[server]
issuer = "https://idp.example.com"
listen = "unix:/run/ahdapa/ahdapa.sock"
Point the proxy’s ProxyPass / proxy_pass at unix:/run/ahdapa/ahdapa.sock|http://localhost/
(Apache) or http://unix:/run/ahdapa/ahdapa.sock (nginx) instead of http://127.0.0.1:8080.
Apache httpd
Required modules
The following modules must be loaded. On Fedora and RHEL they are included
in the httpd and mod_ssl packages; the LoadModule lines are present
in the default configuration under /etc/httpd/conf.modules.d/.
mod_proxy – reverse proxy core
mod_proxy_http – HTTP backend support
mod_headers – RequestHeader / Header directives
mod_remoteip – rewrite REMOTE_ADDR from X-Forwarded-For
mod_ssl – TLS for the public-facing listener
Install if not already present:
dnf install httpd mod_ssl
Virtual host
Place this file at /etc/httpd/conf.d/ahdapa.conf:
# Redirect plain HTTP to HTTPS.
<VirtualHost *:80>
ServerName idp.example.com
Redirect permanent / https://idp.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName idp.example.com
# TLS certificate — replace with your actual paths.
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/idp.example.com.crt
SSLCertificateKeyFile /etc/pki/tls/private/idp.example.com.key
SSLProtocol TLSv1.2 TLSv1.3
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:!aNULL
# Rewrite REMOTE_ADDR so ahdapa's access log shows real client IPs.
RemoteIPHeader X-Forwarded-For
# Forward requests to ahdapa.
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
# Inform ahdapa that the client connection is HTTPS.
# ahdapa uses this to set the Secure flag on session cookies.
RequestHeader set X-Forwarded-Proto "https"
# Optional: restrict gossip paths to cluster peers at the proxy layer.
# Gossip endpoints are protected by CMS authentication (ECDSA P-256 +
# ML-KEM-768) and the allowed_node_ids allowlist, so this block provides
# defence-in-depth only. Remove it if gossip traffic reaches the nodes
# directly rather than through this vhost.
<Location /api/gossip>
Require ip 192.168.0.0/24 # replace with your cluster subnet
</Location>
</VirtualHost>
After editing, reload Apache:
apachectl configtest && systemctl reload httpd
nginx
Installation
dnf install nginx
Server block
Place this file at /etc/nginx/conf.d/ahdapa.conf:
# Redirect plain HTTP to HTTPS.
server {
listen 80;
listen [::]:80;
server_name idp.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name idp.example.com;
# TLS certificate — replace with your actual paths.
ssl_certificate /etc/pki/tls/certs/idp.example.com.crt;
ssl_certificate_key /etc/pki/tls/private/idp.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:!aNULL;
# Pass real client IP to ahdapa's access log.
real_ip_header X-Forwarded-For;
set_real_ip_from 127.0.0.1; # trust the loopback address
# set_real_ip_from 192.168.0.0/24; # add if nginx itself is behind a load balancer
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Increase timeouts for long-polling endpoints (token introspection,
# device flow polling). The defaults (60 s) are usually sufficient.
proxy_read_timeout 120s;
}
# Optional: restrict gossip paths to cluster peers at the proxy layer.
# Gossip endpoints are protected by CMS authentication (ECDSA P-256 +
# ML-KEM-768) and the allowed_node_ids allowlist, so this block provides
# defence-in-depth only. Remove it if gossip traffic reaches the nodes
# directly rather than through this server block.
location /api/gossip {
allow 192.168.0.0/24; # replace with your cluster subnet
deny all;
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
After editing, reload nginx:
nginx -t && systemctl reload nginx
Interaction notes
These notes apply to both Apache and nginx.
Security headers
ahdapa emits Strict-Transport-Security, X-Frame-Options,
X-Content-Type-Options, Referrer-Policy, and Content-Security-Policy
on every response. The headers are set with if_not_present semantics: a
value already present in the response from the proxy wins. You can therefore
override individual headers in the proxy config without modifying ahdapa.
The Content-Security-Policy header includes frame-ancestors 'none' (RFC 9700
§4.16), which prevents ahdapa pages from being embedded in frames or iframes
on any origin.
Referrer-Policy: no-referrer is applied specifically to the /authorize
endpoint (RFC 9700 §4.2.4) in addition to the global Referrer-Policy response
header. This prevents OAuth parameters in the authorization URL (such as state
and code_challenge) from leaking to any resource loaded during the auth flow.
Kerberos / SPNEGO
SPNEGO authentication works unchanged through both proxies. The
Authorization: Negotiate … header is forwarded to ahdapa unmodified.
Do not strip or rewrite the Authorization header in the proxy config.
RFC 5929 TLS channel binding
tls-server-end-point channel binding (RFC 5929) ties authentication to the
TLS session between the client and the server. Behind a TLS-terminating
proxy, ahdapa sees only a plain-text connection from the proxy and cannot
observe the client’s TLS session, so channel binding is not available in this
topology. This is a known limitation of TLS termination at the proxy layer.
Gossip topology
The recommended layout is to have gossip peers communicate directly on an internal network interface rather than through the public proxy vhost:
# node1.toml — gossip uses internal addresses, not the public hostname
[gossip]
peers = ["https://192.168.0.2:8080", "https://192.168.0.3:8080"]
This avoids routing intra-cluster traffic through the public-facing TLS
terminator while still keeping gossip on HTTPS end-to-end. Configure
gossip.ca_cert if the nodes present certificates signed by an internal CA
rather than a public one.
If your network topology requires gossip to traverse the public proxy (for
example, nodes in different data centres with no shared private network),
point the peers at the public HTTPS URLs, ensure gossip.ca_cert is set or
that the public CA is in the system trust store, and optionally tighten the
proxy-layer <Location> / location ACL to the specific peer IP addresses.