Setting Up mTLS for Service-to-Service REST APIs
AI generated
{ }
GET
mTLS · Zero Trust · Microservices
mTLS for Service-to-Service APIs
Authenticating both sides of an internal connection via certificate instead of an API key

API keys for internal service-to-service communication have a structural problem: a stolen key can be used from anywhere, without the server noticing that the request does not actually come from the expected service. Mutual TLS solves this by having both sides of the connection identify each other via certificate, not just the server to the client.

16 min read Mutual TLS · Zero Trust Certificates · PKI

1. Why API keys are not enough for internal communication

With classic TLS, only the server identifies itself to the client: the client checks the server certificate, but the server has no cryptographic guarantee of who is actually on the other end of the connection, beyond whatever an API key or bearer token in the request claims. If that key is compromised, for example through a leak in a config file or a compromised container, an attacker can impersonate the legitimate service from anywhere.

Mutual TLS (mTLS) extends the TLS handshake with a second direction: the client also presents a certificate, which the server checks against a trusted Certificate Authority (CA). An attacker without the matching private key material cannot establish a valid connection even with a stolen API key, because the TLS handshake itself already fails, long before the application layer ever sees a request.

2. Your own certificate authority as the trust anchor

For internal mTLS between your own services, an internal Certificate Authority is usually run instead of using public CAs like Let's Encrypt, since internal service names (order-service.internal) are not publicly resolvable anyway and public CAs would not sign such names. This internal CA signs both server and client certificates for every service in the infrastructure.

Building your own PKI (Public Key Infrastructure) sounds more elaborate than it actually is with modern tools. Tools like HashiCorp Vault's PKI secrets engine or cert-manager in Kubernetes fully automate issuing, renewing, and revoking certificates, so a single team does not have to maintain the internal CA manually with OpenSSL commands.


# Creating an internal CA and service certificate with Vault PKI (simplified)
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki

vault write pki/root/generate/internal \
    common_name="internal-ca.mironsoft.local" \
    ttl=87600h

vault write pki/roles/order-service \
    allowed_domains="order-service.internal" \
    allow_subdomains=true \
    max_ttl="720h"

# Issuing a certificate for order-service
vault write pki/issue/order-service \
    common_name="order-service.internal" \
    ttl="720h"

3. Configuring mTLS in Symfony clients and servers

On the client side, Symfony's HttpClient component configures the client's own certificate and private key through the local_cert and local_pk options, in addition to regular TLS verification of the server certificate against the internal CA. On the server side, the web server (Nginx, Apache) must be configured to demand a client certificate and verify it against the internal CA, before the request is even forwarded to the Symfony application.

The actual Symfony application usually receives information about the validated client certificate through headers set by the web server (e.g. X-SSL-Client-DN), from which the identity of the calling service can be derived, for example for fine-grained authorization decisions beyond pure connection authentication.


<?php
// Configuring Symfony HttpClient with an mTLS client certificate
use Symfony\Component\HttpClient\HttpClient;

$client = HttpClient::create([
    'local_cert' => '/etc/certs/order-service.pem',
    'local_pk' => '/etc/certs/order-service.key',
    'cafile' => '/etc/certs/internal-ca.pem',
    'verify_peer' => true,
    'verify_host' => true,
]);

$response = $client->request('GET', 'https://pricing-service.internal/api/prices/SKU-123');

4. Certificate rotation without downtime

Certificates should be short-lived, typically days rather than years, to minimize the window of exposure of a compromised certificate. Short-lived certificates, however, require automated renewal, since a manual rotation process for hundreds of services every few days is practically infeasible and would regularly cause outages from expired certificates.

Tools like cert-manager in Kubernetes or a Vault agent sidecar renew certificates automatically before expiry and place them at a defined location in the filesystem, without requiring the service itself to restart, as long as the application re-reads the certificate from the filesystem on every new connection instead of caching it permanently in memory.

5. mTLS as a building block of a zero-trust architecture

mTLS is a central building block of the zero-trust security model, where no trust is implied purely based on network position, such as 'inside the internal network, therefore trustworthy.' Instead, every connection, even within the same internal network, must be cryptographically authenticated, regardless of whether the traffic crosses an internal or external network segment.

This mindset is becoming increasingly relevant because classic network segmentation alone is no longer considered a sufficient security boundary, especially in cloud environments with dynamic infrastructure, where IP addresses change frequently and purely IP-based access control becomes unreliable.

6. Service mesh as an alternative to manual mTLS implementation

Instead of manually implementing mTLS in every single service, service mesh solutions like Istio or Linkerd handle the entire mTLS management transparently through sidecar proxies: every service gets a proxy attached that automatically encrypts and authenticates all incoming and outgoing traffic via mTLS, without the application code itself noticing any of it.

The benefit is noticeably less boilerplate code in every individual service and centralized control over mTLS policies. The downside is additional infrastructure complexity from the service mesh itself, which often does not justify the effort for smaller system landscapes with few services.

7. Common pitfalls in practical rollout

A common mistake is not proactively monitoring certificate expiry dates: an expired certificate causes sudden connection failures between services that at first glance look like a network problem rather than a certificate issue. A monitoring alert that warns a few days before a certificate expires reliably prevents such production surprises.

A second common mistake is inadequate certificate revocation handling: if a service is compromised, its certificate must be revocable immediately, not just after its natural expiry. A working Certificate Revocation List (CRL) or OCSP check is therefore not an optional detail, it is an integral part of a production-ready mTLS rollout.

8. Systematically narrowing down mTLS connection failures

mTLS failures usually show up as generic TLS handshake errors without a meaningful application-level error message, which complicates troubleshooting. A structured approach first checks with openssl s_client whether the server even requests a client certificate, then whether the presented client certificate is valid and signed by the expected CA, and only afterward the actual application logic.

Meaningful logs at the web server level (not just in the Symfony application) are crucial, since a failed TLS handshake never reaches the application itself. Nginx and Apache both offer detailed SSL debug logs that show exactly at which point in the handshake the connection was rejected, for example due to an expired or untrusted client certificate.


# Manually testing an mTLS connection and narrowing down failures
openssl s_client -connect order-service.internal:443 \
    -cert /etc/certs/pricing-service.pem \
    -key /etc/certs/pricing-service.key \
    -CAfile /etc/certs/internal-ca.pem

# Checking a certificate's validity and issuer
openssl x509 -in /etc/certs/pricing-service.pem -noout -dates -issuer

9. mTLS compared to other service authentication methods

The table below compares mTLS to other common approaches to service-to-service authentication, to make the choice easier for your own system.

Method Protection if secret is stolen Implementation effort Typical use
API key No protection, usable immediately Low Simple internal setups, low security requirements
Bearer token/JWT No protection until expiry Medium Most common standard for internal APIs
mTLS High, private key still required High without service mesh Zero-trust requirements, regulated industries
mTLS via service mesh High, transparently managed Medium (mesh setup one-time) Larger microservice landscapes

Mironsoft

OpenAPI design, Symfony APIs, and API security

APIs that external teams can integrate without back-and-forth questions?

We review existing REST APIs for inconsistent error formats, missing OpenAPI documentation, and security gaps, then build an API that is clearly documented, versioned, and hardened against abuse.

API Review

Checking the OpenAPI spec, error formats, and status codes for consistency.

Symfony Implementation

Using DTOs, Serializer, and Validator for clean, type-safe request/response models.

Security Audit

Hardening rate limiting, auth schemes, and input validation against real attack surfaces.

10. Summary

mTLS for Service-to-Service APIs: The Essentials at a Glance

Core principle

Both sides of the connection authenticate via certificate, not just the server to the client.

PKI setup

An internal Certificate Authority signs certificates for every service, automated via Vault or cert-manager.

Rotation

Short-lived certificates with automated renewal minimize the risk window of a compromised key.

Service mesh

Istio or Linkerd take over mTLS transparently via sidecar, significantly reducing boilerplate code in every individual service.

11. FAQ: mTLS for Service-to-Service APIs: The Essentials at a Glance

1What is the difference between TLS and mTLS?
With TLS, only the server identifies itself via certificate. With mTLS (mutual TLS), both sides of the connection present a certificate, verified by the other side against a trusted CA.
2Do I need a public CA for internal mTLS certificates?
No, for internal service names, an internal Certificate Authority is usually run, since public CAs would not sign internal, unresolvable domain names anyway.
3How often should mTLS certificates be renewed?
Short-lived certificates in the range of days rather than years minimize the exposure window of a compromised certificate. That requires automated renewal via tools like Vault or cert-manager.
4Does mTLS completely replace API keys and JWTs?
mTLS authenticates the connection at the transport layer. For fine-grained authorization (what actions this service is specifically allowed to perform), an additional token or scope system at the application layer is often still used.
5Is mTLS worth it for a small Symfony project with two services?
With very few services, the configuration effort often outweighs the security gain compared to a simple, well-protected API key. mTLS becomes more clearly worthwhile with a larger number of services or in regulated industries.
6What happens if a certificate expires without being renewed?
The connection fails at the TLS handshake, which often feels like a network problem rather than a certificate issue. Proactive monitoring of upcoming certificate expiry prevents such surprises.
7How do I revoke a compromised certificate immediately?
Through a Certificate Revocation List (CRL) or OCSP check that the server verifies against the current revocation list on every handshake, instead of waiting for the certificate's natural expiry.
8What is a service mesh and how does it help with mTLS?
A service mesh like Istio or Linkerd attaches a sidecar proxy to every service that handles mTLS encryption and authentication transparently, without the application code itself noticing any of it.
9Does mTLS work across cloud provider boundaries?
Yes, as long as both sides use certificates from the same trusted CA hierarchy. For multi-cloud setups, a central, overarching PKI instance is often run for exactly this purpose.
10Is mTLS a replacement for network segmentation?
No, the two measures complement each other. Network segmentation limits who can even attempt a connection, mTLS ensures the connection itself is cryptographically authenticated.