Technology

Dual RSA and ECDSA Certificates on Nginx and Apache

Dual ECDSA and RSA Certificates

Serving dual ECDSA + RSA certificates aims to combine efficient authentication with broader client compatibility. It does not guarantee faster page loads or eliminate every TLS error; client capabilities, certificate trust and server configuration still matter.

This guide prepares separate ECDSA and RSA certificate/key pairs for the same hostnames, configures them in Nginx and Apache, and tests each authentication path. The examples serve public static files; application runtimes or reverse proxies need their own configuration. Certificate negotiation, CA signatures, key agreement and transport protocols are separate decisions.

Benefits and Limits of Dual Certificates

ECDSA can reduce key/signature size and authentication work in some deployments, but the effect depends on hardware, TLS implementation, chain size and connection reuse. Smaller keys alone do not establish a visible page-load improvement.

RSA, meanwhile, offers broad compatibility. It’s widely compatible, even with older clients that don’t understand ECDSA. If you run a site with any meaningful audience, you probably have visitors using older Android builds, legacy Java runtimes, or embedded devices that are surprisingly picky. Serving an RSA certificate alongside ECDSA can help accommodate those clients, provided they also support the configured TLS settings and trust the certificate chain.

The trade-off is operational: both certificate and key pairs need renewal, deployment and testing.

Certificate Selection During TLS Negotiation

The server selects a certificate using the client’s advertised capabilities together with server configuration, available chains and local preferences. The CA signature on an issued certificate is fixed; it is not negotiated anew during the handshake. A separate handshake signature proves possession of the server key. Modern clients do not invariably receive ECDSA, and an RSA option cannot repair unsupported protocols or an untrusted chain.

In TLS 1.2, cipher suites also specify authentication such as ECDHE-ECDSA or ECDHE-RSA. TLS 1.3 separates symmetric cipher selection from signature algorithms and certificate selection. Client certificate-signature support and trust still matter. Installing two certificates does not by itself make TLS 1.3 negotiation faster.

Prepare Two Certificate and Key Pairs

Two keys, two certs, one identity

Prepare an ECDSA key, such as P-256, and an RSA key sized for policy and client needs. Request certificates covering the same required SAN hostnames. ACME clients must keep the RSA and ECDSA certificate records and renewal paths distinct. The following OpenSSL 1.1.1+ example is a standalone script for a fresh workspace; dual-cert-work must not already exist. It creates keys and CSRs, not CA-issued certificates. Do not overwrite deployed keys. Keys are unencrypted and restricted to 0600 permissions.

#!/bin/sh
# Run as a separate script in a directory where dual-cert-work does not exist.
set -eu
umask 077
mkdir -m 700 dual-cert-work
cd dual-cert-work

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out site-ecdsa.key
openssl req -new -sha256 -key site-ecdsa.key -out site-ecdsa.csr \
  -subj '/CN=example.com' \
  -addext 'subjectAltName=DNS:example.com,DNS:www.example.com'

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out site-rsa.key
openssl req -new -sha256 -key site-rsa.key -out site-rsa.csr \
  -subj '/CN=example.com' \
  -addext 'subjectAltName=DNS:example.com,DNS:www.example.com'
chmod 600 site-ecdsa.key site-rsa.key

openssl req -in site-ecdsa.csr -noout -verify -text
openssl req -in site-rsa.csr -noout -verify -text

Submit the CSRs through the CA’s validation process, then verify that issued certificates actually contain the required SANs. The requested extensions are not an issuance guarantee. Deploy each issued certificate with its matching private key and actual CA chain at the example paths, preserving private-key permissions and keeping keys outside the web root.

Use the issuer chain that actually validates each leaf certificate. The issuer’s key type does not have to equal the leaf key type; an ECDSA leaf and an RSA-signed chain are not inherently a mismatch. Verify issuer signatures, intermediates and the client’s trust anchor rather than pairing files by their RSA/ECDSA names.

Signing Keys and Key-Agreement Groups

P-256 is a common ECDSA signing-key choice; RSA 2048 is used in the example, but policy or compatibility requirements may call for a different size. X25519 is a key-agreement group, not an ECDSA signing curve. The supported-groups setting must also remain compatible with the certificate and target clients.

Configure Dual Certificates in Nginx

Nginx supports repeated ssl_certificate directives for different key types since 1.11.0; separate chains require OpenSSL 1.0.2+. The modern http2 on example below requires Nginx 1.25.1+ with SSL and HTTP/2 modules, while TLS 1.3 requires compatible OpenSSL 1.1.1+ support. Use maintained builds rather than interpreting these feature minimums as version recommendations.

Nginx server block example

Adapt domain names and deploy the issued certificate/key files first. This is a static-only example with no PHP handler: use a directory containing only public static assets, and do not put application source there. Script filenames are denied as an additional safeguard. IPv6 listening must be supported by the host. HSTS remains commented until HTTPS and same-host redirects have been verified.

server {
    listen 443 ssl;
    http2 on;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    # Dual certificates: one ECDSA + one RSA
    ssl_certificate     /etc/ssl/certs/example-ecdsa-fullchain.pem;
    ssl_certificate_key /etc/ssl/private/example-ecdsa.key;
    ssl_certificate     /etc/ssl/certs/example-rsa-fullchain.pem;
    ssl_certificate_key /etc/ssl/private/example-rsa.key;

    # Protocols and ciphers
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # Prefer modern curves; X25519 is a great choice, with P-256 as a solid fallback
    ssl_ecdh_curve X25519:prime256v1;

    # Session settings for speed
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Baseline for certificates without an active OCSP service, including Let’s Encrypt.
    ssl_stapling off;

    # HSTS (enable after you’ve confirmed HTTPS everywhere)
    # After HTTPS is verified, opt in with a short lifetime:
    # add_header Strict-Transport-Security "max-age=300" always;

    # Root and basic app location
    root /var/www/example-static;
    index index.html;

    # Static-only example: no PHP handler is configured.
    location ~* \.(php|phtml|phar)(/|$) {
        return 404;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

Certificate directive order alone is not a reliable preference policy. Test which certificate is served for the intended client capabilities and CA chains. Check the default virtual host and clients without SNI as well as the named host. The server selects from compatible options rather than following a universal modern-client/old-client split.

Nginx Chains and Certificate Selection

Pair each leaf with its actual issuing chain; the CA signature algorithm can differ from the leaf key type. Current Let’s Encrypt certificates do not use OCSP because that service retired in August 2025. With another supporting CA, stapling needs responder access and a valid trust bundle. Resolver failure does not establish that all Nginx handshakes stall. The conditional configuration details appear below, separately from the baseline.

Client-certificate authentication is a separate TLS and authorization flow. The Nginx mTLS guide explains that configuration; test it alongside both server-certificate paths instead of assuming dual certificates guarantee compatibility.

Configure Dual Certificates in Apache

Apache can repeat SSLCertificateFile for different server-authentication algorithms. Version 2.4.8 specifically added intermediate-chain certificates in that file, while OpenSSL 1.0.2+ supports per-certificate chains. Use a maintained TLS 1.3-capable Apache 2.4 build with compatible OpenSSL 1.1.1+; the static example needs mod_ssl, mod_headers, mod_http2 and mod_authz_core. HTTP/2 also depends on its library/MPM support. Do not change MPMs without checking application compatibility.

Apache vhost example

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com

    # Dual certificates: ECDSA + RSA
    Protocols h2 http/1.1
    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/example-ecdsa-fullchain.pem
    SSLCertificateKeyFile /etc/ssl/private/example-ecdsa.key
    SSLCertificateFile    /etc/ssl/certs/example-rsa-fullchain.pem
    SSLCertificateKeyFile /etc/ssl/private/example-rsa.key

    # Protocols
    SSLProtocol -all +TLSv1.2 +TLSv1.3

    # Modern curves and sane ciphers
    SSLOpenSSLConfCmd Curves X25519:prime256v1
    SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    SSLHonorCipherOrder off

    # No OCSP service is assumed for this baseline.
    SSLUseStapling Off

    # HSTS (enable after confirming HTTPS-only)
    # After HTTPS verification, optional short HSTS:
    # Header always set Strict-Transport-Security "max-age=300"

    DocumentRoot /var/www/example-static

    <Directory /var/www/example-static>
        AllowOverride None
        Require all granted
    </Directory>
    <FilesMatch "(?i)\.(php|phtml|phar)$">
        Require all denied
    </FilesMatch>
</VirtualHost>

Confirm each certificate matches its private key and covers the required SANs. The static-only directory disables .htaccess overrides and denies common PHP script filenames; no PHP runtime is configured. Applications need their own documented handler or reverse-proxy configuration. A reverse HTTP proxy additionally requires mod_proxy and mod_proxy_http.

TLS Versions, Ciphers and Supported Groups

Support only the TLS versions and cipher suites your policy allows. The examples include both ECDHE-ECDSA and ECDHE-RSA AEAD suites for TLS 1.2. Nginx ssl_ciphers and the unqualified Apache SSLCipherSuite list do not configure TLS 1.3 cipher suites. TLS 1.3 signature and certificate selection is separate. Key-agreement groups such as X25519 must not be confused with certificate-signing keys.

AES-GCM and ChaCha20-Poly1305 performance varies with client/server implementations and hardware acceleration. Both appear in the example list. Measure under representative conditions instead of claiming that one ordering guarantees the best result for all devices.

Renew and Deploy Both Certificates

Configure the selected ACME client to retain separate RSA and ECDSA certificate records with the required SANs. Monitor both renewals and stable deployment paths. Validate the new certificate/key pairs and chains before reloading; successful renewal alone does not prove the server has loaded both new certificates.

For an unmanaged Nginx deployment, run nginx -t before nginx -s reload; for Apache, run apachectl configtest before apachectl graceful. Use the package’s supported service workflow where applicable. Record the outcome and probe new connections for each certificate type. Existing connections do not immediately renegotiate with the new certificate.

Test Both Authentication Paths

Test both certificate paths, chain validation, hostnames and real client groups. A constrained OpenSSL test proves a particular combination, not every old device or runtime. Keep error output and inspect the exit status.

Quick client-side checks

Constrain TLS 1.2 to an ECDSA-authenticated suite while leaving normal signature-algorithm support available for the CA chain:

openssl s_client -connect example.com:443 -servername example.com -tls1_2 -cipher ECDHE-ECDSA-AES128-GCM-SHA256 -verify_hostname example.com -verify_return_error -showcerts < /dev/null

Test the RSA-authenticated TLS 1.2 path separately. This is not a complete simulation of a legacy client:

openssl s_client -connect example.com:443 -servername example.com -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 -verify_hostname example.com -verify_return_error -showcerts < /dev/null

Use the correct client trust store, supplying a CA file if required. showcerts displays the server-sent list; verification errors and return status determine whether hostname/chain checks succeeded. Also test TLS 1.3 without restricting all signatures to one type, then inspect the selected leaf key. An overly narrow sigalgs test can fail because of a CA-chain signature rather than missing leaf support. The following additional probe constrains the key-agreement group to X25519; it does not force an ECDSA certificate:

openssl s_client -connect example.com:443 -servername example.com -tls1_3 -verify_hostname example.com -verify_return_error -showcerts < /dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_3 -curves X25519

Public scans

Use SSL Labs for an external view of both certificate types and protocol settings, then verify representative devices and trust stores. A public scan supplements application and client testing.

Measure Handshake Cost and Failures

Session reuse, HTTP/2 and HSTS affect different parts of delivery. HSTS is disabled in the baseline: after HTTPS and redirects work, begin with a short max-age such as 300, then observe each stage before extending it. Include subdomains only after auditing public and internal hosts; preload is a separate long-term choice. It is not needed to serve dual certificates.

Measure CPU, handshake rate, latency and failures for representative first and repeat connections. A smaller RSA key may change cost, but do not reduce key strength below policy requirements to improve a benchmark. A higher ECDSA selection rate alone is not proof of better performance or compatibility.

Running behind CDNs or load balancers

The component that terminates visitor TLS selects the public certificate. Installing dual certificates on the origin does not determine the CDN edge’s choice. Configure and test edge-to-origin encryption separately. With TLS passthrough, certificate selection remains on the destination Nginx/Apache; verify SNI, ALPN and routing through the load balancer.

Keep a record of the TLS termination point, presented hostnames, trust policy and certificate owner at each hop. Test the actual edge and origin connections instead of assuming a setting automatically propagates between them.

Chain, Renewal and Policy Checks

For an OCSP-capable CA only, the fragments below require correct chain trust and responder access. In Nginx, replace the baseline ssl_stapling off; line with ssl_stapling on; rather than leaving both in the same server block. The trust bundle needs issuers, intermediates and trusted roots required for both certificates; replace the example path and documentation-only resolver IP. In Apache, load mod_socache_shmcb and place SSLStaplingCache globally outside all VirtualHost blocks, using an existing suitable runtime directory. Replace SSLUseStapling Off with SSLUseStapling On in the applicable VirtualHost and add the other per-host directives only where the CA supports OCSP. Do not use these fragments for current Let’s Encrypt certificates.

# Nginx: inside an OCSP-capable certificate server block only.
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/issuers-intermediates-roots.pem;
resolver 192.0.2.53 valid=300s;
resolver_timeout 5s;

# Apache: global configuration, outside ALL VirtualHost blocks.
# Load mod_socache_shmcb; use an existing Apache runtime directory.
SSLStaplingCache shmcb:/path/to/apache-run/ocsp(128000)

# Apache: inside the applicable OCSP-capable VirtualHost only.
SSLUseStapling On
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors Off

Both certificate paths must be deployed and tested. An expired or incorrectly paired secondary certificate may go unnoticed when a default probe selects the other one. Validate renewal and new handshakes for RSA and ECDSA independently.

Official Configuration References

Use the Nginx ssl_certificate reference for multiple-certificate and chain behavior, Apache mod_ssl documentation for certificate/protocol directives, and SSL Labs for external checks. Match references to the deployed version and library.

HTTP/2 and HTTP/3 Requirements

HTTP/2 requires compatible TLS ciphers, ALPN and server-module support; its benefit depends on the workload. HTTP/3 uses QUIC and needs a separately configured supported listener and network path. These examples do not configure it and therefore do not advertise an h3 Alt-Svc endpoint. Verify the certificate configuration on each real listener.

Deployment Checklist

Dual certificates provide additional authentication choices, with additional lifecycle work. Compatibility still depends on protocols, signatures, chains and trust stores; performance must be measured.

Prepare two protected keys and two issued certificates with the required SANs, pair them with valid chains and configure both in the intended virtual host. Validate syntax and each authentication path before gradual deployment. Use OCSP only where supported, and treat HSTS, HTTP/2 and application handling as separate settings.

Monitor both expiries and renewal deployments. Keep a rollback configuration and repeat the same checks after server, library or certificate-chain changes.

Frequently Asked Questions

No. RSA can help, but the client must also support the configured protocols, ciphers, signatures and trust chain. Test representative devices and both certificate paths.

No. The leaf public-key type and the CA signing key are separate. Use the actual issuing chain and validate the client’s trust path.

No. Let’s Encrypt retired OCSP in August 2025. Current certificates are not expected to provide a staple; optional OCSP fragments apply only to supporting CAs.
↑