Mutual TLS (mTLS) for a generated API
Configure mTLS authentication for your RestWard API, including CA, fingerprint, and subject criteria, certificate rollover, per-method rate limits, IP allowlists, and client examples.
Mutual TLS (mTLS) authenticates API consumers using X.509 client certificates. RestWard supports mTLS alongside API keys and JWT, letting you choose the right authentication method for your security requirements.
This guide covers configuring mTLS for a generated API, the available authentication criteria, certificate rollover, per-method rate limits and IP allowlists, and client examples.
Prerequisites
- A RestWard organization with at least one generated API
- mTLS authentication method enabled for your organization
- Client certificates ready for your consumers
How mTLS works in RestWard
RestWard's data-plane APIs are served behind a TLS-terminating reverse proxy (Caddy). The proxy handles the TLS handshake and forwards the client certificate to the RestWard runtime in the X-Forwarded-Client-Cert header as base64-encoded DER. The runtime then validates the certificate against your configured criteria.
Architecture: Caddy terminates TLS -> forwards client cert in
X-Forwarded-Client-Certheader -> RestWard runtime validates against CA/fingerprint/subject criteria.
This means:
- You configure mTLS in the RestWard dashboard, not in the proxy
- The proxy requests but does not validate the client certificate: on the wildcard
*.run.restward.tech, which covers every API's own hostname, Caddy asks for a client certificate inclient_authmoderequest, so it is optional and API-key or JWT requests are unaffected - Certificate validation happens in the runtime using your configured trust anchors
- mTLS can coexist with API key and JWT authentication; the runtime tries each enabled method until one succeeds
Enabling and configuring mTLS
mTLS is configured per organization, not per API. The configuration applies to all APIs in the organization that have the mTLS authentication method enabled.
Source:
src/Domain/Entities/AuthMethodConfiguration.cs:L6-L10—AuthMethodConfigurationhasOrganizationId,Method,IsEnabled, andConfiguration; there is noApiConfigurationId. The same configuration is shared by all APIs in the organization.
Enabling mTLS
- Go to Settings → Authentication Methods
- Find Mutual TLS (mTLS) in the list
- Toggle the switch to Enabled
- Click Save
Configuring mTLS
Once enabled, configure the mTLS authentication method for your organization:
- In Settings → Authentication Methods, click Configure for the mTLS entry
- Configure the criteria:
- Trusted CA (PEM): Paste your CA certificate(s) in PEM format (can include multiple certificates)
- Allowed SHA-256 fingerprints: Add SHA-256 thumbprints of specific client certificates (one per line)
- Allowed subjects: Add subject distinguished names, common names, or SAN DNS names (one per line)
- Optionally configure:
- Default scope: The permission level granted to authenticated clients (Read Only, Read & Write, or Admin)
- Rate limit (/min per certificate): Per-certificate rate limit (defaults to 60 if not set)
- Allowed IP Ranges: CIDR ranges or IP addresses from which certificates are accepted
- Click Save
Source:
src/Web/Components/Pages/AuthMethods/MtlsAuthConfig.razor:L1-L10— The configuration modal is titled "Configure Mutual TLS" and contains fields for Trusted CA, Allowed SHA-256 fingerprints, Allowed subjects, Default scope, Rate limit, and IP allowlist.
You specify which client certificates are accepted using one or more of these criteria:
| Criterion | Description | Format | Notes |
|---|---|---|---|
| Trusted CA | Certificates must chain to this CA | PEM-encoded CA certificate(s) | Required when using subject criteria |
| Allowed Fingerprints | Specific certificate thumbprints to accept | SHA-256 hex strings (with or without colons) | Exact match required |
| Allowed Subjects | Specific subject DNs, CNs, or SAN DNS names to accept | String patterns | Only checked when CA chain validates; subjects alone are not sufficient |
Important: At least one criterion must be configured. If you specify allowed subjects, you must also configure a trusted CA - a subject match without CA validation is not accepted (anyone can self-sign a certificate with any subject).
Configuration steps
- In Settings → Authentication Methods, click Configure for the mTLS entry
- Configure the criteria:
- Trusted CA (PEM): Paste your CA certificate(s) in PEM format (can include multiple certificates)
- Allowed SHA-256 fingerprints: Add SHA-256 thumbprints of specific client certificates (one per line)
- Allowed subjects: Add subject distinguished names, common names, or SAN DNS names (one per line)
- Optionally configure:
- Default scope: The permission level granted to authenticated clients (Read Only, Read & Write, or Admin)
- Rate limit (/min per certificate): Per-certificate rate limit (defaults to 60 if not set)
- Allowed IP Ranges: CIDR ranges or IP addresses from which certificates are accepted
- Click Save
Validation rules
The configuration is validated when saved:
- At least one criterion (CA, fingerprint, or subject) must be present
- If subjects are specified, a trusted CA must also be specified
- Blank entries in fingerprints or subjects lists are ignored
When a client connects:
- If no certificate is presented -> mTLS authentication is not attempted (other methods may still work)
- If a certificate is presented but no criteria are configured -> authentication fails with
401 Unauthorized - If a certificate is presented and criteria are configured:
- Certificate must be within its validity window (not before
NotBefore, not afterNotAfter) - Certificate must match at least one criterion:
- Its SHA-256 thumbprint matches an allowed fingerprint, OR
- It chains to the trusted CA AND (if subjects are configured) its subject matches an allowed subject
- Certificate must be within its validity window (not before
- If accepted -> authentication succeeds with the configured scope and rate limit
- If rejected -> authentication fails with
401 Unauthorizedand reasonnot_acceptedoroutside_validity
Authentication criteria in detail
Trusted CA
The trusted CA certificate(s) form the root of trust for your mTLS configuration. Client certificates must chain to one of these CAs to be accepted (unless they match a fingerprint criterion).
Format: PEM-encoded certificate(s). You can paste multiple CA certificates; they are all added to the trust store.
Example PEM:
-----BEGIN CERTIFICATE-----
MIIFazCCBFOgAwIBAgIRAIIQz7DSQONZqGDYzMiP0w0wDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh
... (certificate content) ...
-----END CERTIFICATE-----Best practices:
- Use intermediate CAs for better certificate management
- Rotate your CA certificates periodically
- Keep CA private keys secure and offline
Allowed Fingerprints
Fingerprint criteria accept client certificates whose SHA-256 thumbprint matches one of the configured values. This is useful when you want to accept specific, known certificates regardless of their CA chain.
Format: SHA-256 hexadecimal string, with or without colons. Both A1B2C3D4... and A1:B2:C3:D4... formats are accepted and normalized for comparison.
Example fingerprints:
A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1D2E3F4A5B6C7D8E9F0A1B2
A1:B2:C3:D4:E5:F6:A7:B8:C9:D0:E1:F2:A3:B4:C5:D6:E7:F8:A9:B0:C1:D2:E3:F4:A5:B6:C7:D8:E9:F0When to use fingerprints:
- You have a small, known set of client certificates
- You want to accept certificates from different CAs
- You need fine-grained control over which specific certificates are accepted
When NOT to use fingerprints:
- You have many clients with frequently rotating certificates
- Managing individual fingerprints becomes burdensome
Allowed Subjects
Subject criteria accept client certificates whose subject distinguished name (DN), common name (CN), or subject alternative name (SAN) DNS entry matches one of the configured values.
Important: Subject matching only applies to certificates that also chain to the trusted CA. A subject match alone is not sufficient for authentication.
Format: String patterns matched against:
- The full subject distinguished name (e.g.,
CN=MyApp, O=MyOrg, C=US) - The common name (CN) from the subject
- The DNS names from subject alternative names (SAN)
Matching is case-insensitive.
Example subjects:
CN=production-app
CN=MyApp, O=MyOrg, C=US
api.client.myorg.comWhen to use subjects:
- You want to accept any certificate from a particular organization or application
- Combined with CA, you can accept all certificates issued by your CA for a specific subject
- You have multiple services using certificates from the same CA
Certificate rollover
When client certificates expire or are compromised, you need to perform a rollover. RestWard supports smooth certificate rollover through overlapping acceptance criteria.
Recommended rollover procedure
- Generate new certificates with the new CA or new key pairs
- Add the new criteria to your mTLS configuration:
- Add the new CA to Trusted CA PEM (if changing CA)
- Add new fingerprints to Allowed Fingerprints
- Add new subjects to Allowed Subjects
- Save the configuration - both old and new certificates are now accepted
- Deploy new certificates to your clients
- Verify all clients are using the new certificates
- Remove old criteria from the configuration once all clients have migrated
Example: CA rollover
# Before rollover
Trusted CA PEM: Old-CA-Cert
# During rollover (both accepted)
Trusted CA PEM: Old-CA-Cert, New-CA-Cert
# After rollover
Trusted CA PEM: New-CA-CertExample: Fingerprint rollover
# Before rollover
Allowed Fingerprints: Old-Cert-Fingerprint
# During rollover (both accepted)
Allowed Fingerprints: Old-Cert-Fingerprint, New-Cert-Fingerprint
# After rollover
Allowed Fingerprints: New-Cert-FingerprintTip: Use CA-based validation with subjects for easier rollover. You can issue new certificates from the same CA with the same subject, and they will be accepted automatically without configuration changes.
Per-method rate limits
Each mTLS configuration can have its own rate limit, applied per authenticated client certificate.
- Default: 60 requests per minute (same as API keys)
- Configurable: Set any value in the mTLS configuration
- Scope: Per certificate thumbprint (SHA-256 hash of the client certificate's DER), across all endpoints of all APIs in the organization. The caller identifier used for the quota is
cert:{thumbprint}as defined inApiKeyRateLimitingMiddleware.CallerId(src/ApiRuntime/Middleware/ApiKeyRateLimitingMiddleware.cs:L179). - Enforcement: Uses the same rate-limiting infrastructure as API keys
When a client exceeds their rate limit, they receive:
- HTTP
429 Too Many Requests Retry-Afterheader with seconds until the window resetsX-RateLimit-Scope: callerheader indicating the caller quota was exceeded
Example rate-limited response:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 42
X-RateLimit-Scope: caller
{"error": "Rate limit exceeded. Try again later."}Note: The per-method rate limit is separate from per-endpoint rate limits. Both apply: a request must pass both the caller quota and any endpoint-specific limit.
IP allowlists
You can restrict mTLS-authenticated clients to specific IP addresses or CIDR ranges. This adds an additional layer of security beyond certificate validation.
Configuration:
- Add IP addresses or CIDR ranges in the Allowed IP Ranges field
- One entry per line
- Supports both IPv4 and IPv6
Examples:
# Single IP
192.168.1.100
# CIDR range
192.168.1.0/24
# IPv6
2001:db8::/32Behavior:
- If no IP ranges are configured, clients can connect from any IP
- If ranges are configured, the client's IP must be in at least one range
- IP check happens after certificate validation
- Rejection returns HTTP
403 Forbiddenwith message "IP address not allowed."
Important: The IP address used is the one resolved by the trusted proxy (after
X-Forwarded-Forprocessing). Ensure your proxy is correctly configured.
Client examples
Prerequisites for all examples
- A RestWard API with mTLS configured, and its endpoint hostname from the API's detail page (the examples write it as
YOUR-API-HOSTNAME) - A client certificate accepted by your configuration
- The certificate's private key
curl
Use curl's --cert and --key options to present a client certificate:
# With separate certificate and key files
curl --cert client-cert.pem --key client-key.pem \
https://YOUR-API-HOSTNAME/api/public/users
# With a combined PEM file (certificate + private key)
curl --cert client-combined.pem \
https://YOUR-API-HOSTNAME/api/public/users
# With certificate, key, and CA bundle for server verification
curl --cert client-cert.pem --key client-key.pem --cacert ca-bundle.pem \
https://YOUR-API-HOSTNAME/api/public/users
# With verbose output to see TLS handshake details
curl -v --cert client-cert.pem --key client-key.pem \
https://YOUR-API-HOSTNAME/api/public/usersNote: curl automatically handles client certificate requests from the server. The --cert option sends the certificate when the server requests it.
.NET (HttpClientHandler)
In .NET, configure the HttpClientHandler to use a client certificate:
using System;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Load the client certificate from a PFX file
// X509Certificate2 constructor with password and key storage flags is obsolete on .NET 9+/10 (SYSLIB0057)
// Use X509CertificateLoader.LoadPkcs12FromFile instead, which does not persist the private key
var certificate = X509CertificateLoader.LoadPkcs12FromFile(
"client-cert.pfx",
"your-certificate-password");
var handler = new HttpClientHandler
{
ClientCertificateOptions = ClientCertificateOption.Manual,
};
// Add the client certificate
handler.ClientCertificates.Add(certificate);
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json");
try
{
var response = await client.GetAsync(
"https://YOUR-API-HOSTNAME/api/public/users");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
}
}Notes:
X509CertificateLoader.LoadPkcs12FromFileis the recommended approach on .NET 9+/10; it loads the certificate without persisting the private key to the machine storeClientCertificateOption.Manualmeans the certificate is sent only when explicitly requested by the server- For automatic certificate selection, use
ClientCertificateOption.Automatic
Node.js (https.Agent)
In Node.js, create a custom HTTPS agent with your client certificate:
const https = require('https');
const fs = require('fs');
// Load certificate and key files
const cert = fs.readFileSync('client-cert.pem');
const key = fs.readFileSync('client-key.pem');
const ca = fs.readFileSync('ca-bundle.pem'); // Optional: for server cert verification
const agent = new https.Agent({
cert: cert,
key: key,
ca: ca, // Optional: CA bundle to verify the server
rejectUnauthorized: true // Set to false only for testing with self-signed certs
});
const options = {
hostname: 'YOUR-API-HOSTNAME',
port: 443,
path: '/api/public/users',
method: 'GET',
agent: agent,
headers: {
'Accept': 'application/json'
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
console.log('Response:', data);
} else {
console.log('Error:', res.statusCode, data);
}
});
});
req.on('error', (error) => {
console.error('Request error:', error);
});
req.end();Alternative using axios:
const axios = require('axios');
const https = require('https');
const fs = require('fs');
const cert = fs.readFileSync('client-cert.pem');
const key = fs.readFileSync('client-key.pem');
const agent = new https.Agent({
cert: cert,
key: key
});
axios.get('https://YOUR-API-HOSTNAME/api/public/users', {
httpsAgent: agent,
headers: {
'Accept': 'application/json'
}
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error:', error.response ? error.response.data : error.message);
});Troubleshooting
Common issues and solutions
| Issue | Cause | Solution |
|---|---|---|
401 Unauthorized - "Mutual TLS is not configured for this API" |
mTLS not configured for the API | Enable and configure mTLS for the API |
401 Unauthorized - "Client certificate rejected" with reason not_accepted |
Certificate doesn't match any criterion | Verify certificate matches CA/fingerprint/subject |
401 Unauthorized - "Client certificate rejected" with reason outside_validity |
Certificate is expired or not yet valid | Check certificate dates and renew if needed |
403 Forbidden - "IP address not allowed" |
Client IP not in allowed ranges | Add client IP to allowed ranges or remove IP restriction |
429 Too Many Requests |
Rate limit exceeded | Wait for the window to reset or increase the limit |
| Connection refused / timeout | Server not configured for mTLS | Verify Caddy is configured with client_auth mode |
Verifying your certificate
Check your certificate's details:
# View certificate details
openssl x509 -in client-cert.pem -text -noout
# Get SHA-256 fingerprint
openssl x509 -in client-cert.pem -fingerprint -sha256 -noout
# Get subject
openssl x509 -in client-cert.pem -subject -noout
# Get SAN (Subject Alternative Names)
openssl x509 -in client-cert.pem -ext subjectAltName -nooutTesting with a self-signed certificate
For testing purposes, you can create a self-signed certificate:
# Generate CA
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=Test CA"
# Generate client certificate
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=test-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256
# Create combined PEM (certificate + key)
cat client.crt client.key > client-combined.pemThen configure RestWard with:
- Trusted CA PEM: contents of
ca.crt - Allowed Subjects:
CN=test-client
Checking the forwarded header
To verify that Caddy is correctly forwarding the certificate, you can temporarily add logging or use a test endpoint. The header should contain the base64-encoded DER of the client certificate.
Related documentation
- Authentication methods - Overview of all authentication options
- API keys & tenant API auth - API key authentication details
- Rate limiting - How rate limits work in RestWard
- RBAC & multi-tenancy - Role-based access control