Currently Available: Need a skilled Software Developer for your next project?
Categories
Self-Hosting

How to Manage Tailscale ACLs as Code With Git and CI

When you manage access in a Tailscale tailnet, the policy file determines which users and devices can reach which destinations. A tailnet is the private network formed by your Tailscale-connected devices. Managing its access control lists (ACLs) as code means storing the policy file in Git, reviewing proposed changes, and having continuous integration (CI) run automated checks when code changes. Those checks test the policy before you apply it.

Run Tailscale’s policy tests on pull requests, then apply the policy after an approved change reaches the protected branch. The workflow also supports grants, a newer way to write access rules. Grants provide ACL functionality and add capabilities, including application-level permissions, so Tailscale recommends them for new policy configurations. You can keep an existing ACL policy and bring it under Git control without rewriting it first.

Put the tailnet policy file in Git

The tailnet policy file describes access rules and related settings. Tailscale uses HuJSON, a JSON-compatible format that permits comments and trailing commas. The Tailscale GitHub Action uses policy.hujson as its default policy filename. Tailscale’s policy syntax reference describes the file’s structure and supported rules.

Tailscale continues to support ACLs indefinitely. Grants provide ACL functionality and add capabilities, including application-level permissions, so Tailscale recommends grants when writing new policy configurations. The Git workflow manages the entire policy file, so an existing ACL policy can remain in place while you adopt version control and CI.

Keep the repository private by default. A policy file can include personally identifiable information, such as user email addresses, and Tailscale recommends a private repository for its GitOps setup.

Test pull requests and apply merged changes

Use separate stages for review and deployment. On a pull request targeting the protected default branch, run the official Tailscale GitHub Action in test mode. It checks policy validity and runs the policy tests without changing the active tailnet policy. After approval and merge, run the Action in apply mode. It tests the policy again and applies it only if the checks pass.

Stage Trigger Action Result
Review Pull request to the protected branch Run test Check the proposed policy without changing the tailnet
Deployment Merge or push to the protected branch Run apply Test the policy again, then update the tailnet if the checks pass

A failed CI check does not automatically prevent a merge. Configure branch protection or equivalent repository rules to require successful checks and reviews before merging. Store the tailnet and credential values in GitHub secrets rather than writing them into the workflow file. Tailscale also supports other CI platforms, so the same test-then-apply design does not depend on GitHub Actions.

Test both allowed and denied access

Add a tests section to the policy file to state which connections should succeed and which should fail. For example, test that an authorized group can reach a specific service while a user outside that group cannot reach a sensitive destination. Tailscale evaluates these assertions when the policy changes and rejects an update when a test fails.

Policy tests check access decisions. They do not check whether an application responds or a service is healthy. Each test needs a specific source, such as a user or device, and a specific destination: wildcard sources and destinations are unsupported, and a test destination cannot use CIDR subnet notation. Use individual IP addresses or hostnames for destination tests. To check whether a service is available, add a separate connectivity or application check to CI.

Tailscale also supports sshTests for Tailscale SSH rules. Those tests cover SSH access decisions, such as whether a user is accepted, checked, or denied; they are distinct from ordinary network access tests.

Limit CI credentials and console edits

Treat credentials that can change the tailnet policy as deployment credentials. Tailscale documents a policy_file:read scope for reading and validating a policy, and a policy_file scope for reading, validating, and modifying it. The documented trust-credential setup also requires additional device-related scopes. Give pull-request checks only the access needed to read and validate the policy. Grant permission to modify the policy only to the deployment stage, if your CI platform supports that separation. See Tailscale’s trust credential scopes before creating credentials.

If you use a Tailscale API access token, store it as a secret and plan its rotation. Tailscale sets a maximum token expiry of 90 days, or less when you choose a shorter expiry, and recommends updating the GitHub secret monthly. Revoke tokens you no longer need.

Keep the Git policy file as the source of truth. Tailscale’s Policy file management setting can prevent edits in the admin console, but an administrator with permission can still choose to edit there. The setting reduces accidental drift; it does not make console changes impossible. If an emergency requires a console edit, copy the resulting policy change back into Git. Otherwise, a later apply can overwrite the console-only edit. Tailscale says the older comment-based locking method stopped working on June 3, 2025.

Roll back through Git

If a merged policy change causes an access problem, revert the pull request in Git and let the deployment stage apply the restored policy. This keeps the active tailnet policy aligned with the reviewed file and preserves a history of the change.

A passing policy test confirms that the tested access assertions hold. It does not confirm that every device can reach every service or that an application works end to end. Add separate checks for those outcomes when they matter to a deployment.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

Your email address will not be published. Required fields are marked *