Access Control
What does ACL do?
Every route in GateControl is publicly accessible via its domain by default. Anyone on the internet can call nas.example.com. ACL changes that:
Without ACL:
Internet (any IP) → Caddy → nas.example.com → Backend ✓
VPN peer 10.8.0.3 → Caddy → nas.example.com → Backend ✓
VPN peer 10.8.0.5 → Caddy → nas.example.com → Backend ✓
With ACL (only peer "Laptop" allowed):
Internet (any IP) → Caddy → nas.example.com → BLOCKED ✕
VPN peer 10.8.0.3 → Caddy → nas.example.com → Backend ✓ (Laptop)
VPN peer 10.8.0.5 → Caddy → nas.example.com → BLOCKED ✕ (iPhone)
ACL effectively makes the route reachable only via VPN and additionally only for selected peers.
How it works internally
Caddy checks the client IP address on every request with the remote_ip matcher (src/services/caddyConfig.js, acl_enabled block):
- Requests over the VPN have a WireGuard IP (e.g.
10.8.0.3) - Requests from the internet have a public IP (e.g.
203.0.113.50) - For each selected peer the first IP from
peers.allowed_ipsis taken and written as a/32range into the matcher (e.g.10.8.0.3/32) - Public IPs are never in the list → always blocked
The ACL configuration is automatically written into the Caddy JSON config. There is no manual configuration.
Use case: Admin panel only for the laptop
Starting point:
You run a Synology NAS at home. GateControl creates the route nas.example.com pointing to port 5001 (DSM Web UI). Without further restriction, anyone on the internet can see the login page of your NAS.
Goal:
Only your work laptop should be able to access nas.example.com. Not your smartphone, not the internet.
Setup:
- Open the route
nas.example.comin the edit modal - Enable Peer Access Control
- In the peer checklist: only select "Office Laptop" (10.8.0.3)
- Save
Result:
| Source | IP at Caddy | Access |
|---|---|---|
| Office Laptop (VPN active) | 10.8.0.3 | Allowed |
| iPhone (VPN active) | 10.8.0.5 | Blocked |
| Any internet user | 203.0.113.x | Blocked |
| Hacker / bot | 45.33.x.x | Blocked |
Your NAS admin panel is now only reachable when you're at your laptop and the VPN tunnel is active.
Further use cases
Development server only for the dev team
Route staging.example.com → Dev server on port 3000
ACL: only peers "Dev-Laptop-1", "Dev-Laptop-2", "Dev-Laptop-3" allowed. QA team and management cannot see the staging environment.
Monitoring dashboard only from the office
Route grafana.example.com → Grafana on port 3000
ACL: only peer "Office-Server" allowed. Employees working from home have no access to the monitoring.
Different services for different groups
| Route | Allowed peers | Purpose |
|---|---|---|
nas.example.com |
Laptop, smartphone | File access for the admin |
admin.example.com |
Laptop only | Sensitive admin panel |
public-api.example.com |
No ACL | Public API without restriction |
Combination with other features
ACL can be combined with other security features:
| Combination | Effect |
|---|---|
| ACL + Route Auth | First check the VPN IP, then login with password/2FA |
| ACL + Rate Limiting | VPN-only access with request limiting |
| ACL + IP Access Control | ACL for VPN peers + IP filter for additional restriction |
| ACL alone | Simplest variant: only selected VPN devices get through |
Important notes
- ACL only blocks access via Caddy (HTTP/HTTPS routes). Direct VPN access between peers is not restricted.
- If a peer is disabled or deleted, it is automatically removed from the ACL.
- ACL only works for HTTP routes, not for L4 (TCP/UDP) routes.
- The ACL check is the first check Caddy performs — before auth, rate limiting or other handlers.
What does it do?
IP Access Control filters requests based on the client IP address before they reach the backend. Unlike Peer ACL (which only filters VPN peer IPs), the IP filter works with any IP address — from the internet, VPN or local network.
Whitelist mode (only allow these IPs):
203.0.113.50 (office IP) → Caddy → Backend ✓ (in whitelist)
198.51.100.10 (home IP) → Caddy → BLOCKED ✕ (not in whitelist)
45.33.32.1 (bot) → Caddy → BLOCKED ✕ (not in whitelist)
Blacklist mode (block these IPs):
203.0.113.50 (office IP) → Caddy → Backend ✓ (not in blacklist)
198.51.100.10 (attacker) → Caddy → BLOCKED ✕ (in blacklist)
45.33.32.1 (CN bot) → Caddy → BLOCKED ✕ (country CN in blacklist)
How it works internally
IP filtering is performed via a forward-auth subrequest to GateControl (Node.js), which runs before the reverse proxy.
Three rule types:
| Type | Example | Check |
|---|---|---|
| Single IP | 203.0.113.50 |
Exact comparison |
| CIDR range | 10.0.0.0/8 |
Bitmask: (clientIP & mask) === (rangeIP & mask) |
| Country code | DE, US, CN |
GeoIP lookup via ip2location.io API |
Flow on every request:
- Caddy forwards the request to GateControl forward-auth
- GateControl extracts the client IP (strips
::ffff:IPv6 prefix) - Checks each rule sequentially (IP → CIDR → Country)
- Country lookup: API call to ip2location.io with 24h cache (max 10,000 entries)
- Whitelist: match → allowed, no match → blocked
- Blacklist: match → blocked, no match → allowed
Rules are stored as JSON in the database:
[
{ "type": "ip", "value": "203.0.113.50" },
{ "type": "cidr", "value": "10.0.0.0/8" },
{ "type": "country", "value": "CN" }
]
Use cases
Allow only the office network
Route internal.example.com → Internal app. Whitelist with CIDR 185.10.20.0/24 (office IP range). Only employees in the office (or via office VPN) can access it.
Block bot traffic from certain countries
Route shop.example.com → Webshop. Blacklist with country codes CN, RU, KP. Significantly reduces automated spam and brute-force attempts.
Block known attacker IPs
Route api.example.com → API. Blacklist with individual IPs that have been identified as attackers in logs. Quick reaction without firewall access.
Combination with other features
| Combination | Effect |
|---|---|
| IP filter + Peer ACL | Double filtering: ACL for VPN peers, IP filter for additional internet IPs |
| IP filter + Rate Limiting | Allowed IPs are additionally rate-limited |
| IP filter + Route Auth | IP check before login — blocked IPs don't even see the login page |
| IP filter + Basic Auth | IP filter is checked via forward auth (does not work with Basic Auth) |
Important notes
- Difference from Peer ACL: ACL only filters WireGuard peer IPs (10.8.0.x). IP Access Control filters any arbitrary IP address.
- Country lookup requires an ip2location.io API key. Without a key, country rules are ignored.
- The GeoIP cache stores up to 10,000 entries for 24 hours. On cache miss an API call is made (max 5 seconds timeout).
- IPv6-mapped IPv4 addresses (
::ffff:192.168.1.1) are automatically reduced to IPv4. - IP Access Control only works with Route Auth or as a standalone forward-auth check. With Basic Auth the IP filter is not available.
- An empty whitelist allows nobody. An empty blacklist blocks nobody.
- IP filter is only available for HTTP routes, not for L4 (TCP/UDP).