Skip to content
Documentation

Reverse proxy security and TLS requirements

This page is the checklist for whoever runs the reverse proxy or load balancer in front of an SSOGEN installation you operate yourself. Follow it top to bottom, then run the six checks in section 9.

The examples use the fictional company Acme Corp with SSOGEN at https://sso.acme.example. SSOGEN’s own port is 9941 unless you chose another at install time.

SSOGEN always serves HTTPS on its own port, never plain HTTP. Browsers must reach the address of your installation over HTTPS.

Shape Address people use Who holds the certificate browsers see
A. Direct. Browsers connect to SSOGEN’s own port. https://sso.acme.example:9941 (the port is part of the address) SSOGEN.
B. Proxy or load balancer in front. It listens on 443 and connects to SSOGEN’s port over HTTPS. https://sso.acme.example (no port) The proxy or load balancer.

The address is fixed when SSOGEN is installed and cannot be changed afterwards, so choose the shape before you install. The rest of this page is about shape B, except section 3.

2. What the proxy must do, and must not do

Section titled “2. What the proxy must do, and must not do”
# The proxy must Why
1 Pass the Host header through unchanged (Apache: ProxyPreserveHost On; nginx: proxy_set_header Host $host;). SSOGEN answers only for its own address. A request for any other host is answered 421. SSOGEN never reads X-Forwarded-Host.
2 Connect to SSOGEN over HTTPS, with TLS 1.3 or TLS 1.2 and one of the cipher suites of section 4. SSOGEN has no plain-HTTP port. A back-end TLS profile that offers only CBC or RSA key exchange fails the handshake, and the proxy answers 502 or 503.
3 Send the client’s address in one header and replace whatever the client sent in that header (section 5). Sign-in policies, network zones, rate limits and the audit trail all use the client address.
4 Redirect HTTP to HTTPS itself. Nothing in SSOGEN redirects port 80.
5 Accept request headers up to 96 KB (Apache: LimitRequestFieldSize 65536) and form bodies of at least 512 KB. Some sign-in requests are large; SSOGEN accepts form bodies up to 384 KB.
6 Allow 30 seconds or more for a response and for an idle connection; 45 seconds or more gives headroom. A refused sign-in is held back on purpose (1.5 seconds by default, about 22 seconds in the worst case). The phone-approval screen asks about every 2 seconds. SSOGEN itself uses no WebSocket, streaming or long polling, so no buffering or upgrade settings are needed.
7 Leave the paths of section 7 reachable, and pass the Authorization header through unchanged. Web Agents, Gateways and provisioning clients call SSOGEN on their own paths.
8 Probe health correctly (section 8). A probe with the wrong Host marks every node down.
# The proxy must not Why
1 Rewrite Host, or route by a header the client can set. Requests are refused with 421, or reach the wrong place.
2 Pass on the client’s own copy of the client-address header. The recorded address would be whatever the client wrote.
3 Be listed more widely than it is. Never list 0.0.0.0/0 as a trusted proxy. Only your proxy’s own address or subnet belongs in the list.
4 Leave SSOGEN’s own port reachable from anywhere but the proxy. A request that goes around the proxy also goes around everything the proxy does. Never publish that port to the internet.
5 Terminate TLS for Gateways (section 6). A Gateway identifies itself in the TLS handshake.
6 Cache /logos/... beyond what SSOGEN’s Cache-Control header allows. SSOGEN decides who may see each image.

Sticky sessions are not needed; a sticky setting does no harm.

3. Shape A: SSOGEN serves browsers directly

Section titled “3. Shape A: SSOGEN serves browsers directly”

The installer creates a self-signed certificate for SSOGEN’s port. A browser that does not trust a self-signed certificate warns, so bring your own certificate for this shape:

--tls-cert /path/to/fullchain.pem --tls-key /path/to/privkey.pem

Add both options to the installer command you use. The certificate must cover the name in the address, the key must be its own key and must not be protected by a passphrase. Your PEM files are only read; SSOGEN keeps its own copy in its keystore.

A later --apply without the two options keeps your certificate untouched. To go back to a generated certificate, run --apply --use-generated-listener-cert. Open SSOGEN’s port to your users in the firewall.

Protocols and ciphers SSOGEN accepts. No setting changes this list. SSOGEN refuses TLS 1.1 and older, CBC suites, DHE suites and RSA key exchange.

Protocol Cipher suites Key-exchange groups
TLS 1.3 Accepted x25519, P-256, P-384
TLS 1.2 ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305 x25519, P-256, P-384

Choose a back-end TLS profile on your load balancer that offers TLS 1.3 or TLS 1.2 with ECDHE and GCM, for the traffic and for the health check.

To see what the port accepts, run these on the SSOGEN server. The first two must connect; the last three must fail.

openssl s_client -connect 127.0.0.1:9941 -tls1_3 </dev/null 2>/dev/null | grep -E '^New|Protocol'
openssl s_client -connect 127.0.0.1:9941 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 </dev/null 2>/dev/null | grep '^New'
openssl s_client -connect 127.0.0.1:9941 -tls1_2 -cipher ECDHE-RSA-AES128-SHA </dev/null
openssl s_client -connect 127.0.0.1:9941 -tls1_2 -cipher DHE-RSA-AES128-GCM-SHA256 </dev/null
openssl s_client -connect 127.0.0.1:9941 -tls1_3 -groups ffdhe2048 </dev/null

The certificate on SSOGEN’s port. In shape B the certificate your users’ browsers trust belongs on the proxy. SSOGEN’s own port uses the certificate the installer generated:

Fact Value
Type Self-signed, RSA 3072, valid 20 years
Names it covers The name in SSOGEN’s address, localhost, 127.0.0.1, ::1, and each name you gave with --listener-san
Public copy for your proxy /etc/ssogen/listener.pem
Fingerprint Printed by the installer’s --check, --plan and --apply
Reissue Only with --renew-listener-cert, or by itself when it is within 30 days of its end or lacks a name you now ask for

Pin that certificate on the proxy. For Apache on the same server:

SSLProxyEngine on
SSLProxyVerify require
SSLProxyCheckPeerName on
SSLProxyCheckPeerExpire on
SSLProxyCACertificateFile /etc/ssogen/listener.pem
ProxyPreserveHost On
ProxyPass / https://127.0.0.1:9941/ retry=0 connectiontimeout=5 timeout=30
ProxyPassReverse / https://127.0.0.1:9941/

For a proxy or load balancer on another host:

  1. When you install SSOGEN, add --listener-san <name> for each name or address the proxy uses to connect. The address the proxy connects to must be a name on the certificate.
  2. Copy /etc/ssogen/listener.pem to the proxy and compare its SHA-256 fingerprint with the one the installer printed.
  3. Configure the proxy to trust exactly that certificate for its connection to SSOGEN.
  4. After the certificate is reissued, copy the file again. On Apache, reload with apachectl graceful. A proxy that still pins the old certificate reports a TLS error or answers 502.

If your load balancer cannot validate a back-end certificate, it can still connect, but it then cannot tell SSOGEN from something else answering on that address. Keep the link between the two on a private network you control, bind SSOGEN to one address, and block SSOGEN’s port from everywhere else with the firewall.

Bind and firewall SSOGEN’s port. With a proxy on the same server, install with --listen-address 127.0.0.1 and confirm that the port shows 127.0.0.1 only:

ss -ltn

With a proxy on another host, open the port to the proxy’s address only.

On the proxy host with SELinux enforcing, Apache needs permission to connect out:

setsebool -P httpd_can_network_connect on
semanage port -a -t http_port_t -p tcp <port>

SSOGEN must be told which addresses are your proxies and which header carries the client’s address. Three settings do that. Give them to the installer, or set them in /etc/ssogen/ssogen-platform.env and restart; they are read at start.

Setting Installer option Meaning Default
SSOGEN_TRUSTED_PROXY_CIDRS --trusted-proxy (repeatable) The addresses or subnets SSOGEN sees your proxy connect from, as address/prefix. No host names. Empty: no proxy; the connection’s own address is the client.
SSOGEN_TRUSTED_PROXY_HOPS --trusted-proxy-hops N How many proxies add to the header between the browser and SSOGEN: 1 to 8. A value above 1 works only with X-Forwarded-For or Forwarded. 1
SSOGEN_CLIENT_IP_HEADER --client-ip-header NAME The header your proxy writes: X-Forwarded-For, Forwarded, X-Real-IP, CF-Connecting-IP, True-Client-IP or X-Client-IP. X-Forwarded-For
sudo systemctl restart ssogen

Rules for the list: a private range may be as wide as its block; any other range may be at most a /12 (IPv4) or a /28 (IPv6). Two short forms exist: --trusted-proxy private (the three private IPv4 blocks of RFC 1918 and the IPv6 unique local block fc00::/7) and --trusted-proxy google-cloud (130.211.0.0/22,35.191.0.0/16).

In front of SSOGEN Hops Trusted proxies
Apache httpd or nginx, on the same server or another 1 The web server’s address (127.0.0.1/32 on the same server)
One load balancer that adds the header (AWS Application Load Balancer, Azure Application Gateway, HAProxy, Traefik, Envoy) 1 The balancer’s address or subnets
A Google Cloud external Application Load Balancer 1 --trusted-proxy google-cloud
A CDN, then a load balancer that adds the header 2 The load balancer’s address or subnets and the CDN’s published ranges
A load balancer, then Apache or nginx on the SSOGEN server, both adding the header 2 The web server’s address and the load balancer’s address or subnets
A layer-4 load balancer that preserves the client address Leave empty Leave empty
A front end that neither keeps the client address nor adds a header Not supported. Make it send the header, then set 1 and the front end’s address.

Product settings are as the vendors publish them; check the names in your version’s manual.

Product Set on it On SSOGEN
Apache httpd RequestHeader unset X-Forwarded-For early and ProxyPreserveHost On Apache’s address
nginx proxy_set_header X-Forwarded-For $remote_addr; and proxy_set_header Host $host; nginx’s address
HAProxy http-request set-header X-Forwarded-For %[src], or option forwardfor HAProxy’s address
AWS Application Load Balancer Keep the X-Forwarded-For processing mode “append” The subnets the balancer is placed in
AWS Network Load Balancer Turn client IP preservation on for the target group. SSOGEN does not read PROXY protocol. Nothing: do not list the balancer
Azure Application Gateway Nothing extra The gateway’s subnet
Azure Front Door Restrict the origin to your own Front Door profile Front Door’s published egress ranges (service tag AzureFrontDoor.Backend); with a gateway behind it, that subnet too and hops 2
F5 BIG-IP (terminating TLS) Enable “Insert X-Forwarded-For” in the HTTP profile The SNAT pool or self-IP subnet
Cloudflare Allow the proxy’s port from Cloudflare’s published ranges only Cloudflare’s ranges, and SSOGEN_CLIENT_IP_HEADER=CF-Connecting-IP
Traefik Leave forwardedHeaders.insecure off Traefik’s address
Envoy use_remote_address: true on the outermost Envoy Envoy’s address

When a load balancer’s addresses change, trust the subnet it is placed in, not its current addresses. If it moves to a new subnet, add the new subnet to SSOGEN_TRUSTED_PROXY_CIDRS and to the firewall before the move, and restart SSOGEN.

Where to see the result. In the Admin Console, System health has a Client address card under Configuration that shows what is set and warns when the settings do not match the traffic. Network zones depend on these settings: see Network zones.

6. Gateways, Web Agents and client certificates

Section titled “6. Gateways, Web Agents and client certificates”
Caller Through a proxy that terminates TLS? What to do
Web Agent and Java agent Yes. They send their agent key in the Authorization header. Do not block /agent/..., and pass Authorization through unchanged.
SSOGEN Gateway (all roles) No. A Gateway identifies itself with a certificate in the TLS handshake, which a proxy that terminates TLS does not pass on. Gateways must connect to SSOGEN’s own HTTPS port, not to a proxy that terminates TLS.
Path Used by Rule
/, /login, /apps/api, /apps/remote, /account/api, /logos/... People Public.
/oauth2/..., /oidc/..., /saml/..., /.well-known/... Applications and identity providers Public.
/admin Administrators SSOGEN does not require it to be public. Limiting it by network at the proxy is your choice; run the six checks of section 9 afterwards.
/agent/... Web Agents and Java agents Do not block. Pass Authorization through unchanged.
/edge/... SSOGEN Gateways Do not block. See section 6.
/scim/... An identity provider that provisions people Only if you use SCIM. Limit it to that provider’s addresses if you can.
/connector/... The connector service Only if you use it.
/setup The first administrator Reachable until setup is done; after that it refuses everything.
/health/live, /health/ready, /actuator/health The load balancer For the balancer only.
Probe Answer
/health/ready 200 with an empty body when the node can serve; 503 when it cannot.
/health/live 200 while the process runs.
/actuator/health 200 (also when its status is DEGRADED); 503 for DOWN.

Send SSOGEN’s own host name in the Host header of the probe, or probe /actuator/health. /health/ready and /health/live answer 421 for any other host, an IP address included, which would mark every node down.

When a node is stopped it finishes the requests in flight for up to 20 seconds. Set the balancer’s drain time to at least that.

Replace <your-domain> with the name in SSOGEN’s address. Run them from a machine outside the proxy’s network.

  1. Host preserved. This prints 200:

    curl -s -o /dev/null -w '%{http_code}\n' https://<your-domain>/health/ready

    The same path under another name that points at the proxy must print 421, never 200:

    curl -sk -o /dev/null -w '%{http_code}\n' --resolve other.example:443:<proxy-address> https://other.example/health/ready
  2. Agents and Gateways can reach their paths (if you use them). The answer must come from SSOGEN; a refusal for lack of credentials is the expected one. A timeout, or a 403 or 404 from the proxy itself, means the path is blocked.

    curl -s -o /dev/null -w '%{http_code}\n' https://<your-domain>/agent/configuration
  3. HTTP goes to HTTPS. This prints a 301 or 302 that points at https://<your-domain>/:

    curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://<your-domain>/
  4. The client address is recorded. Sign in as a test person from a machine whose public address you know. In the Admin Console open Monitoring, Audit: the From column of that sign-in shows that machine’s address, not the proxy’s.

  5. A header sent by the client changes nothing. From the same machine, sign in as the test person with a wrong password while your client adds these headers to the sign-in request: X-Forwarded-For: 203.0.113.7, X-Real-IP: 203.0.113.7, CF-Connecting-IP: 203.0.113.7 and Forwarded: for=203.0.113.7. In Monitoring, Audit, the From of that refused sign-in must be your own address, never 203.0.113.7. If it shows 203.0.113.7, the proxy passes the client’s header on, or the hop count is higher than the number of proxies: correct that first. Repeat the check from a machine on your internal network, through the proxy: its From must be that machine’s own address.

  6. SSOGEN’s port is not reachable from outside. This must time out or be refused:

    curl -k --connect-timeout 5 https://<ssogen-host>:9941/health/live

    If it answers, restrict the port to the proxy in the firewall.

Run checks 4, 5 and 6 again after any change to the proxy or to the three settings of section 5.