This episode covers advanced ACLs: granular per-node and per-user rules, tag-based policies with the least privilege principle, plus a hardening checklist like key expiry, device monitoring, Taildrop and SSH restrictions, and securing public server nodes.

In episode 6 you used basic ACLs, and in episode 13 you built the identity layer. Now it's time to combine everything into a tailnet that's genuinely hard against attacks. Episode 14 is the most practical security episode in this series.
We cover advanced ACLs: granular per-node and per-user rules, tag-based policies with the least privilege principle, plus a hardening checklist you can apply right away — key expiry, device monitoring, Taildrop and SSH restrictions, and securing publicly exposed server nodes.
Tailscale ACLs can be granular down to one user accessing one node on specific ports:
{
"acls": [
{ "action": "accept",
"src": ["user1@example.com"],
"dst": ["nas-home:22,80,443"] },
{ "action": "accept",
"src": ["group:devops"],
"dst": ["tag:server:*"] }
]
}The first rule restricts user1@example.com to just three ports on the NAS. The second gives group:devops full access to all server nodes. The port detail in the first rule is the kind of granularity you want to replicate.
The least privilege principle: grant the minimum access a task requires. Combining tags and groups makes it easy:
{
"tagOwners": {
"tag:db": ["group:devops"],
"tag:web": ["group:devops"]
},
"acls": [
{ "action": "accept",
"src": ["tag:web"],
"dst": ["tag:db:5432"] },
{ "action": "accept",
"src": ["group:devops"],
"dst": ["tag:web:22"] }
]
}Above, web nodes may only touch port 5432 of database nodes, and humans only come in via port 22. There's no open *:* rule — every path is considered explicitly.
tailscale logout or remove from the admin console for retired devices.tailscale status regularly to spot strange nodes.tailscale status
tailscale status --json | grep -c '"Online"'The combination of tailscale status with a JSON filter helps count currently online nodes.
The admin console provides the device list, connection status, and activity logs. Make this monitoring routine: unknown devices, status changes, and suspicious activity should be spotted quickly. Combine with the audit log (episode 21) for a complete trail.
Two features that are often left wide open but should be restricted:
{
"acls": [
{ "action": "accept",
"src": ["group:devops"],
"dst": ["tag:server:5201"] }
],
"ssh": [
{ "action": "check",
"src": ["group:devops"],
"dst": ["tag:server"],
"users": ["autogroup:nonroot"] }
]
}Taildrop is restricted via port 5201, SSH is limited to non-root only. With the group:devops and autogroup:nonroot pattern, the attack surface shrinks dramatically.
Nodes also exposed to the internet (VPS, web servers) need extra attention:
sudo tailscale set --auto-updatetailscale ping from nodes that should be denied.Review ACL -> Test access -> Audit devices -> Update policy -> RepeatHardening isn't a one-time event; it's a habit. With granular ACLs, least privilege, and a disciplined checklist, your tailnet stays secure even as it keeps growing.
Episode 14 closed out the security foundation: you can assemble granular ACLs, apply least privilege with tags, and run a hardening checklist that keeps the tailnet safe.
Key takeaways:
In the next episode, episode 15, we'll cover Tailscale Serve and Funnel — exposing a local service only to the tailnet with automatic TLS via MagicDNS, plus exposing it securely to the public internet with Funnel, complete with the differences between the two.