Skip to content

Instantly share code, notes, and snippets.

@jpawlowski
Last active January 31, 2026 03:18
Show Gist options
  • Select an option

  • Save jpawlowski/a20b5dc4f2df787ed67caf4ca451bde1 to your computer and use it in GitHub Desktop.

Select an option

Save jpawlowski/a20b5dc4f2df787ed67caf4ca451bde1 to your computer and use it in GitHub Desktop.
PowerDNS API Proxy configuration for NGINX, including API navigation via RapiDoc and API key management.
# /etc/nginx/sites-available/pdns-api
#
# `ln -s /etc/nginx/sites-available/pdns-api /etc/nginx/sites-enabled/pdns-api`
map_hash_bucket_size 128;
# --- MAPS GO HERE ---
map $http_x_api_key:$remote_addr $internal_pdns_key {
default "";
include /etc/powerdns/api-keys.map;
}
map $uri $ext_accept_header {
default "application/json";
"~*\.json$" "application/json";
"~*\.ya?ml$" "application/x-yaml";
}
# --- SERVER BLOCK ---
server {
listen 443 ssl;
server_name fancyname.ns.example.com;
include snippets/snakeoil.conf;
# Real IP & Proxy Headers
set_real_ip_from 192.0.2.1;
real_ip_header X-Forwarded-For;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Custom Error Pages
error_page 403 = @empty_403;
error_page 404 = @json_404;
# 1. Smart Docs (HTML vs Raw)
location /api/docs {
if ($http_accept ~* "json|yaml") {
error_page 418 = @raw_docs;
return 418;
}
default_type text/html;
return 200 '<!doctype html><html><head><meta charset="utf-8"><script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script><title>$host</title></head><body><rapi-doc spec-url="/swagger.json" render-style="focused" show-header="false" allow-server-selection="false"><img slot="nav-logo" src="/.resources/pdns-logo.svg"/></rapi-doc></body></html>';
}
location /.resources/pdns-logo.svg {
proxy_pass https://upload.wikimedia.org/wikipedia/commons/9/9e/Logo_of_PowerDNS.svg;
proxy_set_header Host upload.wikimedia.org;
}
# 2. Raw Docs Handler
location @raw_docs {
include snippets/pdns-secret.conf;
rewrite ^ /api/docs break;
proxy_pass http://127.0.0.1:8081;
}
# 3. Regex Path for .json
location ~* ^/(openapi|swagger)\.json$ {
include snippets/pdns-secret.conf;
proxy_set_header Accept $ext_accept_header;
# Inject servers list (could be multiple)
sub_filter_once on;
sub_filter_types application/json;
sub_filter '"swagger": "2.0",' '"swagger": "2.0", "servers":[{"url":"https://secondary.ns.example.com/api/v1","description":"site-dns01 (internal DNS)"}],';
# CORS
add_header "Access-Control-Allow-Origin" "https://$host,https://secondary.ns.example.com" always;
add_header "Access-Control-Allow-Methods" "GET, OPTIONS" always;
add_header "Access-Control-Allow-Headers" "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-API-Key" always;
rewrite ^ /api/docs break;
proxy_pass http://127.0.0.1:8081;
}
# 4. Standard API v1
location /api/v1/ {
if ($internal_pdns_key = "") { return 403; }
include snippets/pdns-secret.conf;
proxy_pass http://127.0.0.1:8081;
}
location /api {
return 302 /api/docs;
}
location /metrics {
rewrite ^ /metrics break;
proxy_pass http://127.0.0.1:8081;
}
# 5. Fallback
location = / {
default_type text/html;
return 200 '<!doctype html><html><head><meta charset="utf-8"><script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script><title>$host</title></head><body><rapi-doc spec-url="/swagger.json" render-style="focused" show-header="false" default-api-server="https://$host/api/v1"><img slot="nav-logo" src="/.resources/pdns-logo.svg"/></rapi-doc></body></html>';
}
# Error Handlers
location @empty_403 {
add_header Content-Type application/problem+json always;
return 403 '{"type":"https://$host/probs/forbidden","title":"Forbidden","status":403,"detail":"Invalid API Key or IP address"}';
}
location @json_404 {
add_header Content-Type application/problem+json always;
return 404 '{"type":"https://$host/probs/not-found","title":"Not Found","status":404}';
}
}
# /etc/nginx/snippets/pdns-secret.conf
#
# `chmod 640 /etc/nginx/snippets/pdns-secret.conf; chown :www-data /etc/nginx/snippets/pdns-secret.conf`
proxy_set_header X-API-Key "changeme";
# /etc/powerdns/api-keys.map
#
# `chmod 640 /etc/powerdns/api-keys.map; chown :www-data /etc/powerdns/api-keys.map`
"MyExampleAPIkey:192.0.2.1" "site-fw01-v4";
"MyExampleAPIkey:2001:db8:0:0:8:800:200c:417a" "site-fw01-v6";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment