Consul Connect mTLS: Securing Service-to-Service Traffic Without Touching Your App Code
Consul Connect's mTLS secures service-to-service traffic via Envoy sidecars and a built-in CA, no app code changes needed. Here's how it works, a working config, and the trade-offs.
03 Dec 2025, 02:39 UTC

You have a dozen microservices talking to each other over plain HTTP inside your cluster. Compliance says traffic must be encrypted and authenticated. The naive answer — every team wires TLS into their own service, manages certs, handles rotation — means duplicated effort, inconsistent cipher configs, and expired certificates at 3 a.m. Consul Connect's mutual TLS (mTLS) feature exists precisely to take that problem off application teams' plates.
The short version: Connect puts a sidecar proxy (Envoy) next to each service, and the proxies encrypt and authenticate all service-to-service traffic using certificates issued by Consul's built-in CA. Your application keeps speaking plain HTTP to localhost; the mesh handles the rest.
How the moving parts fit together
Three components do the work:
- Sidecar proxies. Each service instance gets an Envoy proxy registered alongside it. Inbound and outbound traffic flows through the proxy, which terminates and originates TLS.
- The Connect CA. Consul's built-in certificate authority signs short-lived X.509 certificates for each service identity. CA state is replicated through the Raft consensus cluster, so certificate issuance survives server failures. Rotation happens automatically in the background.
- Intentions. These are Connect's allow/deny rules, integrated with Consul's ACL system. An intention like "web may talk to api" is enforced at the proxy: even a perfectly valid TLS connection from an unauthorized service gets rejected.
This last point is what makes it mutual TLS with actual access control, not just encryption. Both sides present certificates encoding a service identity (a SPIFFE-style URI), and the receiving proxy checks that identity against intentions before passing traffic through.
A minimal worked example
Assume Consul 1.6 or later (Connect doesn't exist before that) with a running cluster and ACLs enabled. First, register a service with a sidecar. This goes in a service definition file on the agent hosting the service, e.g. /etc/consul.d/api.hcl:
service {
name = "api"
port = 8080
connect {
sidecar_service {
proxy {
upstreams = [
{
destination_name = "db"
local_bind_port = 9191
}
]
}
}
}
}Reload the agent (consul reload, run on the agent host with appropriate permissions). The application still listens on 8080 unchanged. To reach db, the app now calls localhost:9191 — the only code-level change is pointing at the local bind port instead of a remote address.
Second, allow the traffic. Intentions default-deny when ACLs are enabled with the default policy set accordingly, so create one explicitly:
consul intention create api dbRun this from any machine with a Consul CLI and a token that has intention:write on those services. To verify the mesh is actually encrypting, check the proxy's configuration and the CA state:
consul connect envoy -sidecar-for api -admin-bind localhost:19000 &
curl -s localhost:19000/config_dump | grep -i tlsYou should see TLS contexts in the Envoy config referencing certificates delivered via Consul's xDS integration. You can also confirm the CA is live by checking agent logs for Connect certificate activity. Never assume encryption is on because registration succeeded — verify at the proxy.
The trade-offs you should plan for
Latency and resource cost. Every hop now passes through two proxies with TLS handshakes. Connection reuse keeps this modest in steady state, but it's not zero, and each sidecar consumes memory and CPU. Budget for it.
Certificate rotation pauses. Certificates are short-lived by design. Rotation is normally graceful, but misconfigured or aggressively short TTLs can cause brief connection drops. Test rotation behavior under load in staging, not production.
ACL lockout. Because intentions default-deny in a hardened setup, a missing or wrong intention silently breaks service communication. Roll out incrementally: enable Connect for one service pair, verify, then expand. Keep an emergency ACL token with intention:write somewhere safe.
It's L4/L7 identity, not end-to-end app auth. mTLS authenticates services, not users. If your app needs per-user authorization, that concern stays in the application layer.
Where to start
Pick your two chattiest, least risky services, register sidecars for both, create one intention, and watch the Envoy admin endpoint while you drive test traffic. Once you've seen rotation happen cleanly and measured the latency delta, you'll have real numbers to justify (or push back on) a wider rollout — which is a much better position than a slide deck.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.