Site icon GetPageSpeed

NGINX Kerberos Authentication: Enterprise SSO with SPNEGO

NGINX Kerberos Authentication: Enterprise SSO with SPNEGO

Every system administrator managing an enterprise network knows the frustration: users must type their credentials into yet another login prompt, even though they already authenticated when they logged into their workstation. Help desk tickets pile up with password reset requests. Sensitive credentials travel over the network in base64-encoded Basic authentication headers that any packet sniffer can decode. In Active Directory environments, this problem is especially painful because a perfectly good authentication infrastructure — Kerberos — already exists but goes unused by the web server. Implementing NGINX Kerberos authentication changes this entirely.

With the SPNEGO module, NGINX participates directly in your existing Kerberos realm, enabling transparent Single Sign-On (SSO). Domain-joined browsers like Chrome, Firefox, and Edge automatically negotiate authentication using the user’s existing Kerberos ticket — no password prompt, no credentials sent over the wire, no extra login step. The user simply opens the intranet page and is immediately authenticated.

This guide covers everything you need to set up NGINX Kerberos authentication: from installation and keytab creation to advanced features like principal-based authorization and constrained delegation. If you are looking for other NGINX authentication options, see also our articles on Phantom Token authentication and JWT authentication.

How SPNEGO Authentication Works

SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) is the protocol that enables Kerberos authentication over HTTP. Understanding the authentication flow helps you troubleshoot issues and make informed configuration decisions.

Here is what happens when a user visits a protected page:

  1. Initial request: The browser sends a normal HTTP request without credentials.
  2. 401 challenge: NGINX responds with HTTP 401 Unauthorized and a WWW-Authenticate: Negotiate header.
  3. Ticket request: The browser requests a service ticket from the Key Distribution Center (KDC) for the NGINX server’s Service Principal Name (SPN).
  4. Token exchange: The browser re-sends the request with an Authorization: Negotiate <base64-token> header containing the Kerberos service ticket.
  5. Validation: NGINX decrypts the ticket using its keytab file, validates the user’s identity, and grants access.
  6. Response: NGINX sets $remote_user to the authenticated principal and forwards the request to the backend.

This entire exchange happens automatically and invisibly in domain-joined browsers. The user sees the page load with no login prompt.

SPNEGO vs. Basic Authentication

Feature SPNEGO/Kerberos Basic Auth
Password sent over network No (ticket-based) Yes (base64-encoded)
User interaction required None (transparent) Password prompt
Requires TLS for safety No (already encrypted) Absolutely yes
Works with Active Directory Native integration Requires separate user store
Single Sign-On Yes No

Installation

RHEL, CentOS, AlmaLinux, Rocky Linux

Install the module from the GetPageSpeed RPM repository:

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-spnego-http-auth

Then load the module at the top of your nginx.conf:

load_module modules/ngx_http_auth_spnego_module.so;

You also need the Kerberos client libraries for keytab management:

sudo dnf install krb5-workstation

Debian and Ubuntu

First, set up the GetPageSpeed APT repository, then install:

sudo apt-get update
sudo apt-get install nginx-module-spnego-http-auth

On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.

Install the Kerberos client tools:

sudo apt-get install krb5-user

Prerequisites

Before configuring NGINX, you need a working Kerberos environment with a keytab file for NGINX.

1. Configure the Kerberos Client

Edit /etc/krb5.conf on the NGINX server to point to your KDC. For an Active Directory domain EXAMPLE.COM:

[libdefaults]
    default_realm = EXAMPLE.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true

[realms]
    EXAMPLE.COM = {
        kdc = dc01.example.com
        admin_server = dc01.example.com
    }

[domain_realm]
    .example.com = EXAMPLE.COM
    example.com = EXAMPLE.COM

2. Create the Service Principal

On your Active Directory domain controller (or MIT KDC), create a service principal for the NGINX server. The SPN must match the hostname that clients use to access the server.

Active Directory (using setspn on Windows):

setspn -A HTTP/intranet.example.com NGINX-SVC

MIT Kerberos (using kadmin):

kadmin -q "addprinc -randkey HTTP/intranet.example.com@EXAMPLE.COM"

3. Generate the Keytab File

Export the service principal’s key into a keytab file that NGINX can read.

Active Directory (using ktpass on Windows):

ktpass -princ HTTP/intranet.example.com@EXAMPLE.COM -mapuser NGINX-SVC@EXAMPLE.COM -crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL -pass * -out http.keytab

MIT Kerberos:

kadmin -q "ktadd -k /etc/nginx/http.keytab HTTP/intranet.example.com@EXAMPLE.COM"

4. Deploy the Keytab

Copy the keytab to the NGINX server and set strict permissions:

sudo cp http.keytab /etc/nginx/http.keytab
sudo chown root:nginx /etc/nginx/http.keytab
sudo chmod 440 /etc/nginx/http.keytab

The keytab contains the service’s secret key. Treat it like a private key file — if compromised, an attacker can impersonate your service.

5. Verify the Keytab

Confirm the keytab contains the correct principal:

klist -k /etc/nginx/http.keytab

Expected output:

Keytab name: FILE:/etc/nginx/http.keytab
KVNO Principal
---- --------------------------------------------------------------------------
   2 HTTP/intranet.example.com@EXAMPLE.COM

Basic Configuration

The simplest NGINX Kerberos authentication configuration protects a location block with SPNEGO:

server {
    listen 443 ssl;
    server_name intranet.example.com;

    ssl_certificate /etc/nginx/ssl/intranet.crt;
    ssl_certificate_key /etc/nginx/ssl/intranet.key;

    # Kerberos tokens can be large — increase header buffer size
    large_client_header_buffers 8 32k;

    location / {
        auth_gss on;
        auth_gss_realm EXAMPLE.COM;
        auth_gss_keytab /etc/nginx/http.keytab;
        auth_gss_service_name HTTP;

        proxy_set_header X-Remote-User $remote_user;
        proxy_set_header Authorization "";
        proxy_pass http://backend;
    }
}

Key points about this configuration:

Advanced Configuration

Restrict Access to Specific Principals

By default, any user who can obtain a valid service ticket is granted access. Use auth_gss_authorized_principal to restrict access to specific users:

location /admin {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;

    auth_gss_authorized_principal admin@EXAMPLE.COM;
    auth_gss_authorized_principal ops@EXAMPLE.COM;

    proxy_pass http://admin-backend;
}

Authenticated users not in the allowed list receive a 403 Forbidden response.

Regex-Based Principal Authorization

For more flexible access control, use auth_gss_authorized_principal_regex to match principals with regular expressions:

location /team {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;

    # Allow any principal starting with "team-"
    auth_gss_authorized_principal_regex "^team-.*@EXAMPLE\.COM$";

    proxy_pass http://team-backend;
}

You can combine exact and regex principals in the same location. A request is authorized if the authenticated principal matches any of the configured entries.

Disable Basic Authentication Fallback

By default, the module sends both WWW-Authenticate: Negotiate and WWW-Authenticate: Basic headers. This allows non-domain-joined clients to fall back to password authentication via Kerberos. However, Basic authentication sends credentials in cleartext (base64) and should be disabled when security is paramount:

location / {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;
    auth_gss_allow_basic_fallback off;

    proxy_pass http://backend;
}

With auth_gss_allow_basic_fallback off, only the Negotiate challenge is sent. Clients that do not support SPNEGO will see a 401 error and cannot authenticate.

Credential Delegation

Credential delegation allows NGINX to forward the user’s Kerberos credentials to a backend application. This is useful when the backend needs to access other Kerberos-protected services on behalf of the authenticated user (for example, a web application that queries a Kerberized database).

location /app {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;
    auth_gss_delegate_credentials on;

    fastcgi_param KRB5CCNAME $krb5_cc_name;
    fastcgi_pass unix:/var/run/php-fpm.sock;
}

When credential delegation is enabled, the module stores the delegated credentials in a temporary credential cache file and exposes its path via the $krb5_cc_name variable. The backend application uses this environment variable to locate and use the delegated credentials.

Important: Credential delegation requires that the client’s Kerberos configuration allows forwarding tickets and that the service principal is trusted for delegation in the KDC.

Constrained Delegation (S4U2proxy)

Constrained delegation is a more secure alternative to full credential delegation. Instead of forwarding the user’s TGT, the service obtains a service ticket on behalf of the user using the S4U2proxy Kerberos extension. This limits which services the NGINX server can access on the user’s behalf.

location /api {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;
    auth_gss_delegate_credentials on;
    auth_gss_constrained_delegation on;
    auth_gss_service_ccache /tmp/krb5cc_nginx;

    fastcgi_param KRB5CCNAME $krb5_cc_name;
    fastcgi_pass unix:/var/run/php-fpm.sock;
}

In Active Directory, you must configure the NGINX service account for constrained delegation in the “Delegation” tab of the account properties, specifying which target services are allowed.

Local Name Mapping

The auth_gss_map_to_local directive applies Kerberos local name mapping rules (defined in krb5.conf) to the authenticated principal. This converts a Kerberos principal like jdoe@EXAMPLE.COM to a local username like jdoe:

location / {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;
    auth_gss_map_to_local on;

    proxy_set_header X-Remote-User $remote_user;
    proxy_pass http://backend;
}

The mapping rules in krb5.conf control the transformation. For example, to strip the realm for local users:

[realms]
    EXAMPLE.COM = {
        auth_to_local = RULE:[1:$1@$0](.*@EXAMPLE\.COM)s/@.*//
        auth_to_local = DEFAULT
    }

Controlling the $remote_user Format

By default, the module strips the realm from $remote_user. So a user authenticated as jdoe@EXAMPLE.COM appears as simply jdoe. To include the full principal with realm:

auth_gss_format_full on;

With this enabled, $remote_user contains jdoe@EXAMPLE.COM.

The auth_gss_force_realm directive ensures that the configured realm is always applied, even when a user authenticates with a different realm:

auth_gss_force_realm on;

Directive Reference

All directives can be used in http, server, location, and limit_except contexts unless noted otherwise.

Directive Default Description
auth_gss off Enable or disable SPNEGO/Kerberos authentication.
auth_gss_realm none The Kerberos realm name (e.g., EXAMPLE.COM).
auth_gss_keytab /etc/krb5.keytab Path to the keytab file containing the service key.
auth_gss_service_name none Service principal prefix (typically HTTP).
auth_gss_service_ccache none Path for the service credential cache, used with constrained delegation.
auth_gss_allow_basic_fallback on Send a Basic challenge alongside Negotiate, allowing password-based Kerberos authentication.
auth_gss_authorized_principal none Allow only the specified principal. Repeatable for multiple principals.
auth_gss_authorized_principal_regex none Allow principals matching the given PCRE regular expression.
auth_gss_format_full off Include the Kerberos realm in $remote_user (e.g., user@REALM).
auth_gss_force_realm off Force the configured realm on all authenticated users.
auth_gss_map_to_local off Apply Kerberos local name mapping rules from krb5.conf.
auth_gss_delegate_credentials off Accept and store delegated user credentials.
auth_gss_constrained_delegation off Enable S4U2proxy constrained delegation.
auth_gss_zone_name shm_zone Name of the shared memory zone used by the module. http context only.

Variables

Variable Description
$remote_user The authenticated Kerberos principal. Format depends on auth_gss_format_full and auth_gss_map_to_local settings.
$krb5_cc_name Path to the delegated credential cache file (e.g., FILE:/tmp/jdoe_EXAMPLE.COM). Only set when auth_gss_delegate_credentials is enabled and the client delegates credentials.

Performance Considerations

NGINX Kerberos authentication has minimal performance overhead compared to other authentication methods:

Security Best Practices

Disable Basic Authentication Fallback

The single most important hardening step is disabling Basic auth fallback. Basic auth sends passwords in base64 encoding, which is trivially decoded. Even over TLS, this exposes credentials to man-in-the-middle attacks if TLS termination happens upstream:

auth_gss_allow_basic_fallback off;

Protect the Keytab File

The keytab file is the security foundation of SPNEGO authentication. Apply strict filesystem permissions:

sudo chown root:nginx /etc/nginx/http.keytab
sudo chmod 440 /etc/nginx/http.keytab

On systems with SELinux enabled, ensure the keytab has the correct context:

sudo restorecon -v /etc/nginx/http.keytab

Use Principal-Based Authorization

Do not rely solely on successful Kerberos authentication for access control. Any domain user can obtain a service ticket. Use auth_gss_authorized_principal or auth_gss_authorized_principal_regex to restrict access to authorized users:

location /sensitive {
    auth_gss on;
    auth_gss_realm EXAMPLE.COM;
    auth_gss_keytab /etc/nginx/http.keytab;
    auth_gss_service_name HTTP;
    auth_gss_allow_basic_fallback off;

    auth_gss_authorized_principal_regex "^(admin|ops-team)@EXAMPLE\.COM$";

    proxy_pass http://sensitive-backend;
}

Prefer Constrained Delegation Over Full Delegation

If your backend needs to act on behalf of the user, prefer constrained delegation (auth_gss_constrained_delegation on) over full credential delegation. Constrained delegation limits which services can be accessed and is controlled at the KDC level, reducing the blast radius of a compromised NGINX server.

Keep Clocks Synchronized

Kerberos is extremely sensitive to clock skew. A difference of more than 5 minutes between the client, NGINX server, and KDC will cause authentication failures. Ensure NTP is running on all systems:

sudo systemctl enable --now chronyd

Strip Credentials Before Forwarding

Always clear the Authorization header before proxying to backend services. This prevents the Kerberos token from being logged, cached, or misinterpreted by the backend:

proxy_set_header Authorization "";

Troubleshooting

Kerberos tokens in Active Directory can be very large, especially for users in many groups. Increase the header buffer size:

large_client_header_buffers 8 32k;

If tokens still exceed 32 KB (users in hundreds of groups), increase to 64k.

“gss_accept_sec_context() failed”

This is the most common error. Check these in order:

  1. Clock synchronization: Run date on the NGINX server and compare with your KDC. Skew > 5 minutes causes failures.
  2. SPN mismatch: The SPN in the keytab must match the hostname in the URL. If users access intranet.example.com, the keytab must contain HTTP/intranet.example.com@EXAMPLE.COM.
  3. Keytab permissions: NGINX must be able to read the keytab. Check with sudo -u nginx klist -k /etc/nginx/http.keytab.
  4. DNS resolution: Reverse DNS for the NGINX server’s IP should resolve to the hostname in the SPN.

Browser Shows Password Prompt Instead of SSO

Ensure the browser trusts the site for Negotiate authentication:

Firefox: Navigate to about:config and set network.negotiate-auth.trusted-uris to your domain (e.g., .example.com).

Chrome/Edge: These browsers use the system Kerberos configuration. On Linux, ensure the user has a valid TGT (klist) and that the site is in the Kerberos-trusted zone.

All browsers: The URL hostname must exactly match the SPN. Accessing by IP address will not trigger SPNEGO.

Detecting NTLM Fallback

If a client sends an NTLM token instead of a Kerberos ticket, the module logs:

Detected unsupported mechanism: NTLM

NTLM is not supported. Ensure the client has a valid Kerberos TGT and that the SPN is correctly configured. NTLM fallback typically occurs when the client cannot contact the KDC or the SPN does not exist.

Enable Debug Logging

For detailed authentication diagnostics, enable debug-level logging:

error_log /var/log/nginx/error.log debug;

The module logs each step of the SPNEGO negotiation, including the decoded principal name, keytab lookup results, and GSSAPI error codes.

Conclusion

NGINX Kerberos authentication with the SPNEGO module eliminates password prompts for enterprise users, strengthens security by removing credentials from the wire, and integrates natively with Active Directory and MIT Kerberos environments.

The module is available from the GetPageSpeed repository for RHEL-based distributions and from the APT repository for Debian and Ubuntu.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version