Authentication
Secure your Cntrl API with API keys and IP whitelisting.
Security Warning
Authentication alone may not make Cntrl safe for the public internet. We strongly
recommend against exposing port 9990 via router port forwarding. Use a VPN
(Tailscale/WireGuard) for remote access.
By default, Cntrl's API is open to anyone on your network. If you are on a shared network (like a dorm or office) or using a VPN like Tailscale, you should enable authentication to secure endpoints.
How it works
Whitelisted IPs bypass the API key check entirely. This means trusted devices (like
a dashboard server) can use EventSource or WebSocket without custom headers, while
external clients still need the API key.
Quick Setup
Add this to your config.json:
{
"auth": {
"enabled": true,
"api_key": "your-secret-key-here",
"allowed_ips": ["192.168.1.100"],
"blocked_ips": []
}
}The IP 192.168.1.100 (your dashboard server, for example) will bypass the API key
check entirely.
IP Blocking (Blacklist)
You can block specific IPs from accessing your API entirely. Blocked IPs are checked first, even when authentication is disabled.
{
"auth": {
"enabled": false,
"blocked_ips": ["192.168.1.50", "10.0.0.0/8"]
}
}Blocked IPs Take Priority
If an IP appears in both allowed_ips and blocked_ips, it will be blocked. The blocklist is always checked first.
Making Authenticated Requests
Include your API key in the Authorization header:
curl -H "Authorization: Bearer your-secret-key-here" \
http://your-pc:9990/api/systemPublic Endpoint
The /api/status endpoint is always public for health checks—no authentication
required.
IP Whitelisting
For additional security, you can restrict access to specific IP addresses:
{
"auth": {
"enabled": true,
"api_key": "your-secret-key-here",
"allowed_ips": ["192.168.1.100", "192.168.1.0/24"]
}
}Supported Formats
| Format | Example | Description |
|---|---|---|
| Single IP | 192.168.1.100 | Exact IP match |
| CIDR | 192.168.1.0/24 | Subnet range |
| Wildcard | * | Allow all IPs (only API key checked) |
Common Configurations
Tailscale Network
If both your PC and dashboard are on Tailscale:
{
"auth": {
"enabled": true,
"api_key": "your-secret-key-here",
"allowed_ips": ["100.64.0.0/10"]
}
}Local Network Only
Restrict to your home network:
{
"auth": {
"enabled": true,
"api_key": "your-secret-key-here",
"allowed_ips": ["192.168.1.0/24"]
}
}API Key Only (No IP Restriction)
Allow any IP with the correct key:
{
"auth": {
"enabled": true,
"api_key": "your-secret-key-here",
"allowed_ips": ["*"]
}
}Dashboard Integration
JavaScript / TypeScript
const API_KEY = "your-secret-key-here";
const API_URL = "http://your-pc:9990";
// Regular fetch
const response = await fetch(`${API_URL}/api/system`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
// SSE Stream
const eventSource = new EventSource(
`${API_URL}/api/stream?fields=cpu,memory`,
// Note: EventSource doesn't support custom headers natively
// See workaround below
);SSE and Authentication
The browser's EventSource API doesn't support custom headers. For authenticated SSE streams, you have two options:
- Use IP whitelisting - Add your dashboard's IP to
allowed_ipsand skip the API key check for SSE - Use a fetch-based SSE client like eventsource-parser
WebSocket Authentication
The WebSocket endpoint (/api/ws) follows the same authentication rules. Use IP
whitelisting for browser WebSocket connections, or pass the API key via a query
parameter if needed.
Fetch-based SSE Example
async function streamWithAuth(url, apiKey, onMessage) {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
onMessage(JSON.parse(line.slice(6)));
}
}
}
}
// Usage
streamWithAuth(
"http://your-pc:9990/api/stream?fields=cpu,memory",
"your-secret-key-here",
(data) => console.log(data),
);Error Responses
| Status | Meaning |
|---|---|
401 Unauthorized | Missing or invalid API key |
403 Forbidden | IP is blocked, or IP not in whitelist (when enabled) |
No Error Body
For security reasons, 401 and 403 responses do not include a JSON body with details.
Generating a Secure Key
Use a random string generator to create a strong API key:
# Linux/macOS
openssl rand -base64 32
# PowerShell
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))Key Length
A 32-character random string is more than enough for a home network. For production, consider using a UUID or a 64-character hex string.