Consul Connect Intentions: Defining Service-to-Service Trust Without Network Firewalls
Consul Connect intentions replace IP-based firewall rules with identity-based allow/deny policies evaluated after mTLS. This blog walks through creating, testing, and debugging a web→api intention, and highlights the default-deny trade-offs you'll face in production.
26 Apr 2026, 17:54 UTC

The problem: zero-trust between services without IP allowlists
When you run dozens of services across multiple clusters or clouds, traditional network segmentation becomes a bottleneck. You end up managing security groups, firewall rules, and IP allowlists that drift out of sync with service deployments. Consul Connect shifts this to identity-based authorization: each service gets a TLS certificate tied to its Consul service name, and intentions decide whether service A can talk to service B—regardless of where they run.
How intentions work
An intention is an ACL rule with three fields: source (the calling service), destination (the target service), and action (allow or deny). Consul evaluates intentions after the mutual TLS handshake but before forwarding application traffic. The model is default-deny: if no matching intention exists, the connection is dropped. This eliminates implicit trust but means every new service pair needs an explicit allow rule.
Worked example: web → api
Assume two services registered in Consul: web (frontend) and api (backend). Both run with a Consul Connect sidecar proxy (Envoy). First, create an intention that permits the traffic:
consul intention create -allow web apiRun this from any machine with a Consul CLI configured to talk to your Consul servers (requires ACL token with intentions:write permission). Verify the rule exists:
consul intention listExpected output shows web => api (allow). Now test from the web sidecar. Exec into the web pod or container and curl the api through its local sidecar listener (typically port 9090 for HTTP):
kubectl exec -it web- -c web -- curl -s http://localhost:9090/healthA 200 response confirms the intention allows traffic. Change the intention to deny and repeat:
consul intention update -deny web api
kubectl exec -it web- -c web -- curl -s http://localhost:9090/healthThe curl now fails with connection refused or TLS handshake error. Check the Consul agent logs on the web node for INTENTION_DENIED entries to confirm enforcement.
Trade-offs and limitations
- Default-deny requires discipline. Every new service or renamed service needs an intention. Rolling updates that change service names (e.g.,
api-v2) will silently break until you add a rule. - Sidecar enforcement only. Traffic that bypasses the sidecar—direct host-to-host, legacy VMs without proxies, or external clients terminating TLS outside the mesh—is not subject to intention checks. You must ensure all mesh traffic flows through sidecars.
- Wildcard intentions. You can use prefixes like
web-* => apito reduce rule count, but overly broad patterns can accidentally permit unintended callers. Audit withconsul intention list -format=jsonand review matches.
Verification checklist
- List intentions:
consul intention list— confirm source, destination, action. - Test allowed path: exec into source sidecar, curl destination via localhost sidecar port.
- Flip to deny, repeat curl, observe failure.
- Check agent logs for
INTENTION_DENIEDto prove enforcement point.
If all four steps behave as expected, your intention policy is active and correctly scoped.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.