Consul ACLs aren’t just for authentication; they’re the primary mechanism for authorizing which services can talk to which other services, and what they’re allowed to do.

Let’s see it in action. Imagine you have a web service and a database service.

First, we need to enable ACLs in Consul. In your Consul configuration, you’ll have something like this:

acl = {
  enabled = true
  default_policy = "deny"
  tokens {
    agent_master = "00000000-0000-0000-0000-000000000001" # Bootstrap token
  }
}

The default_policy = "deny" is crucial. By default, nothing is allowed. We then explicitly grant permissions.

Now, let’s create a policy for our web service. This policy will allow it to read from the database service.

consul policies write web-policy.hcl

Where web-policy.hcl contains:

name = "web-policy"
description = "Policy for the web service"

policy {
  description = "Allow web to read from database"
  rules = {
    "service" = {
      "database" = {
        "policy" = "read"
      }
    }
  }
}

Next, we create a token for the web service and associate it with this policy.

consul tokens resolve -policy-name=web-policy -name=web-token

This command will output a token, let’s say a1b2c3d4-e5f6-7890-1234-abcdef123456. You’ll then configure your web service to use this token. For a client agent, this might be in its configuration:

acl_token = "a1b2c3d4-e5f6-7890-1234-abcdef123456"

When the web service registers with Consul or makes a request (like querying the health of the database service or looking up its network address), it will present this token. Consul will check the web-policy associated with the token and see that service "database" { policy = "read" } is permitted.

Now, if web tries to write to database (which isn’t allowed by its policy), Consul will deny the request with a forbidden error.

The core problem Consul ACLs solve is fine-grained authorization in a distributed system. Instead of relying on network firewalls or implicit trust, you define explicit permissions for every interaction. This aligns with the principle of least privilege, meaning services only get the exact permissions they need to function.

When you create a policy, you’re defining a set of rules. These rules can grant or deny access to specific Consul API endpoints, service registrations, health checks, key-value store entries, and even other services. The service stanza in the policy is particularly powerful for service-to-service communication. You specify the target service name (e.g., database) and the level of access (read, write, list, deny, manage).

The bootstrap token, often called the agent_master token, is special. It’s generated when ACLs are first enabled and has full administrative privileges. You should secure this token rigorously and use it only to bootstrap your ACL system by creating other tokens and policies. For day-to-day operations, you’ll create tokens with more restricted scopes.

There are different types of tokens:

  • Client Tokens: Used by applications and services to interact with Consul. They are associated with one or more policies.
  • Management Tokens: Used by operators to manage Consul itself, including creating/deleting policies and tokens. The bootstrap token is a management token.
  • Service Tokens: A special type of client token specifically for service identities.
  • Node Tokens: A special type of client token specifically for node identities.

You can also configure a default token for agents. If an agent doesn’t have an explicit acl_token set, it will use the node_token for its own registration and health checks if one is configured.

The consul acl command-line tool is your primary interface for managing ACLs. You can create, read, update, and delete policies and tokens. You can also use consul tokens resolve to see the effective policies for a given token, which is incredibly useful for debugging.

When a service tries to perform an action, Consul checks the token presented against the relevant policy. The rules map in a policy can be very granular. For example, to allow a service named frontend to only read the config/frontend key in the KV store, and nothing else:

policy {
  rules = {
    "key" = {
      "config/frontend" = {
        "policy" = "read"
      }
    }
  }
}

The surprising part about Consul ACLs is how they integrate with service discovery. When service A wants to find service B, it typically asks Consul for service B’s address. If ACLs are enabled and service A doesn’t have a token that permits service "B" { policy = "read" }, Consul won’t even return service B’s address, effectively preventing the connection at the discovery layer. This is a powerful, implicit security boundary.

The next step you’ll likely encounter is managing secrets and sensitive data using Consul’s Key/Value store with ACLs, or integrating Consul’s identity into external systems.

Want structured learning?

Take the full Consul course →