mTLS (mutual TLS)

Mutual TLS (mTLS) adds client certificate verification on top of normal server TLS. Ferron supports mTLS in two directions:

  • Inbound — require clients connecting to Ferron to present a valid certificate (server-side mTLS).
  • Outbound — present a client certificate when Ferron connects to an upstream backend via the reverse proxy (client-side mTLS).

Require client certificates

Configure client certificate validation against your internal CA inside the tls block:

# Replace "admin.example.com" with your domain name.
admin.example.com:443 {
    tls {
        provider manual
        cert "/etc/ssl/certs/admin.example.com.crt"
        key "/etc/ssl/private/admin.example.com.key"

        client_auth true
        client_auth_ca "/etc/ssl/internal-client-ca.pem"
    }

    proxy http://127.0.0.1:9000
}

You can also use the OS trust store or Mozilla’s root bundle:

admin.example.com:443 {
    tls {
        provider manual
        cert "/etc/ssl/certs/admin.example.com.crt"
        key "/etc/ssl/private/admin.example.com.key"

        client_auth true
        client_auth_ca system  // or "webpki" for Mozilla's root bundle
    }

    proxy http://127.0.0.1:9000
}
Warning

When client_auth_ca system is used, the OS trust store includes all OS-trusted root CAs — use it only when you want to accept client certificates from any publicly trusted CA (rarely the right choice for mTLS).

Important

For internal mTLS deployments, use a private CA and set client_auth_ca to the CA bundle file path. Keep private internal CA material protected and rotate client certificates regularly.

mTLS with TLS 1.3 only

For maximum security, combine mTLS with TLS 1.3-only settings:

internal-api.example.com:443 {
    tls {
        provider manual
        cert "/etc/ssl/certs/internal-api.example.com.crt"
        key "/etc/ssl/private/internal-api.example.com.key"

        min_version TLSv1.3
        max_version TLSv1.3

        client_auth true
        client_auth_ca "/etc/ssl/internal-ca-bundle.pem"
    }

    proxy http://127.0.0.1:9000
}

Scope planning for admin/internal endpoints

client_auth is configured inside a tls block, which is scoped to a specific host. This means you can enable mTLS for some hosts while keeping others public — no separate Ferron instance is needed.

# Public website — no client auth
example.com:443 {
    tls {
        provider acme
        challenge http-01
        contact "admin@example.com"
    }

    root /var/www/html
}

# Internal admin — requires client certificate
admin.example.com:443 {
    tls {
        provider manual
        cert "/etc/ssl/certs/admin.example.com.crt"
        key "/etc/ssl/private/admin.example.com.key"

        client_auth true
        client_auth_ca "/etc/ssl/internal-client-ca.pem"
    }

    proxy http://127.0.0.1:9000
}

Presenting client certificates to upstream backends

When the reverse proxy connects to an HTTPS upstream, Ferron can present a client certificate to authenticate itself to the backend. Configure cert and key on the upstream block:

example.com {
    proxy {
        upstream https://backend.internal:443 {
            cert "/etc/ferron/client-cert.pem"
            key "/etc/ferron/client-key.pem"
        }
    }
}

Both cert and key must be provided for mTLS to activate. The certificate chain and private key must be PEM-encoded.

Per-upstream credentials

mTLS credentials are scoped per-upstream, so different backends can require different client certificates:

example.com {
    proxy {
        upstream https://service-a.internal:443 {
            cert "/etc/ferron/service-a-client.crt"
            key "/etc/ferron/service-a-client.key"
        }
        upstream https://service-b.internal:443 {
            cert "/etc/ferron/service-b-client.crt"
            key "/etc/ferron/service-b-client.key"
        }
    }
}

mTLS with SRV upstreams

When using SRV-record-based upstreams, the same mTLS credentials are applied to all backends resolved from that SRV record:

example.com {
    proxy {
        srv _https._tcp.backend.internal {
            cert "/etc/ferron/client-cert.pem"
            key "/etc/ferron/client-key.pem"
        }
    }
}

Health checks and mTLS

Active health check probes use the same mTLS credentials configured on the upstream, ensuring probes accurately reflect backend reachability with client authentication.