Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.