Wa
Networking Azure

whois-api-server on Ubuntu 24.04 on Azure User Guide

| Product: whois-api-server on Ubuntu 24.04 LTS on Azure

Overview

whois-api-server (the open source KincaidYang/whois) is a self-hosted WHOIS and RDAP query API. It looks up domains, IPv4 and IPv6 addresses, CIDR prefixes and AS numbers against the live WHOIS and RDAP registries on demand, parses the reply into clean, structured RDAP-vocabulary JSON, and caches it. Because a WHOIS proxy answers from the registries themselves, the image queries the public WHOIS (TCP 43) and RDAP (HTTPS 443) servers over the outbound network — there is no dataset to ship or keep up to date.

The server is a single self-contained native Go binary. It runs behind nginx as a reverse proxy on port 80. The API listens on 127.0.0.1:8043 and, because the upstream binary binds every interface, the image adds an nftables rule that fences the app port to the loopback interface, so the only public surface is nginx.

whois-api-server is a public-by-design lookup service: the WHOIS/RDAP lookup and the web UI need no login because there is no default credential to leak, so none ships in the image or is generated at first boot. On the first boot of every deployed VM a one-shot service resolves the VM public address, writes an instance info file to /root/whois-api-server-info.txt, records the bootstrap-ready marker, and starts the API and the front door. The API unit is gated on that marker, so it never serves before first boot has completed.

To make the image useful the moment it boots, cloudimg bundles a fully self-contained lookup web UI, served locally by nginx, that runs a query in the browser and renders the parsed registry data with no external CDN.

The bundled cloudimg lookup web UI showing a real WHOIS/RDAP result for google.com — registrar, IANA ID, registration and expiry dates, status and nameservers

What is included:

  • whois-api-server 1.2.0 (single native Go binary) at /opt/whois-api-server/whois-server
  • nginx reverse proxy on :80 in front of the API on loopback :8043, with a strict path allowlist, version disclosure suppressed and per-IP rate limiting on the lookup path
  • A self-contained cloudimg lookup web UI served at / (vanilla HTML/JS, no external CDN) that queries the API and renders the result
  • In-memory caching (no Redis dependency by default), so repeated queries do not re-hit upstream registries
  • whois-api-server.service and nginx.service as systemd units, enabled and active, plus a whois-api-server-firstboot.service one-shot that runs once per VM, and an nftables.service loopback fence for the app port
  • The API runs as a dedicated non-root whois service account with no shell and no sudo
  • A fully patched Ubuntu 24.04 LTS security baseline at capture time, with unattended security updates enabled
  • 24/7 cloudimg support

Key facts: platform Ubuntu 24.04 LTS on Azure, default SSH user azureuser, binary /opt/whois-api-server/whois-server, config /etc/whois/config.yaml, web UI and lookup API on port 80, API loopback on 8043.

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point: the service is a small Go binary with an in-memory cache, so it needs little memory. NSG inbound: allow 22/tcp from your management network and 80/tcp (plus 443/tcp once you place your own TLS terminator in front) from the networks that will consume the API. The VM needs outbound access to the internet so it can reach the upstream WHOIS (43/tcp) and RDAP (443/tcp) servers.

Step 1 — Deploy from the Azure Marketplace

Find the cloudimg whois-api-server on Ubuntu 24.04 LTS offer in the Azure Marketplace and select Get It Now, then Create. Choose your subscription, resource group and region, pick Standard_B2s, and provide your SSH public key for the azureuser account. On the Networking tab attach the VM to your VNet/subnet and a network security group that allows inbound 22/tcp from your management network and 80/tcp from your API consumers. Review and create.

Step 2 — Deploy from the Azure CLI

You can deploy the same image from the CLI. Create the network, then the VM from the cloudimg image plan (replace the placeholder names for your environment):

az group create --name whois-rg --location uksouth

az network vnet create --resource-group whois-rg --name whois-vnet \
  --address-prefix 10.20.0.0/16 --subnet-name whois-subnet --subnet-prefix 10.20.1.0/24

az network nsg create --resource-group whois-rg --name whois-nsg
az network nsg rule create --resource-group whois-rg --nsg-name whois-nsg \
  --name allow-ssh --priority 1000 --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create --resource-group whois-rg --nsg-name whois-nsg \
  --name allow-http --priority 1010 --destination-port-ranges 80 --access Allow --protocol Tcp

az vm create --resource-group whois-rg --name whois-vm \
  --image <cloudimg-whois-api-server-image-urn> \
  --size Standard_B2s --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --vnet-name whois-vnet --subnet whois-subnet --nsg whois-nsg --public-ip-sku Standard

Step 3 — Connect to your VM

ssh azureuser@<vm-ip>

The first-boot service runs automatically. Its message-of-the-day banner and /root/whois-api-server-info.txt show the URLs for this VM.

Step 4 — Verify the stack

Confirm the two services are active, that nginx is public on :80 while the API is on :8043, and that the nftables rule fences the app port to loopback:

systemctl is-active whois-api-server nginx whois-api-server-firstboot
ss -tln | grep -E ':80 |:8043 '
sudo nft list ruleset | grep -A4 'table inet whois_filter'

Expected output:

active
active
active
LISTEN 0  511   0.0.0.0:80   0.0.0.0:*
LISTEN 0  4096        *:8043       *:*
LISTEN 0  511      [::]:80      [::]:*
table inet whois_filter {
    chain input {
        type filter hook input priority filter; policy accept;
        iif "lo" accept
        ct state established,related accept
        tcp dport 8043 drop

The API binds :8043 on all interfaces, but the tcp dport 8043 drop rule (for anything not arriving on lo) means it is reachable only through nginx or from on-box localhost.

The two services active, nginx public on :80 with the API on :8043, and the nftables rule fencing the app port to loopback

The health probe confirms the in-memory cache backend:

curl -s http://127.0.0.1/health
{"status":"ok","timestamp":"2026-08-08T11:18:13Z","uptime":"3m35s","checks":{"cache":{"status":"ok","message":"memory"}}}

Step 5 — Open the lookup web UI

Browse to http://<vm-ip>/. The cloudimg lookup page lets you query any domain, IP address, CIDR prefix or AS number and renders the parsed registry data. You can also link straight to a result with the query in the URL fragment, for example http://<vm-ip>/#8.8.8.8.

The lookup web UI rendering the RDAP network object for 8.8.8.8

Step 6 — Query a domain over the API

Every lookup is also a plain HTTP GET. The /api/ path is the rate-limited nginx front door to the query engine; ask it for a domain and you get structured RDAP JSON with the registrar, the registration and expiry dates, the domain status codes and the nameservers:

curl -s http://127.0.0.1/api/google.com | jq '{ldhName,registrar,registrarIanaId,registrationDate,expirationDate,status,nameservers}'
{
  "ldhName": "google.com",
  "registrar": "MarkMonitor Inc.",
  "registrarIanaId": "292",
  "registrationDate": "1997-09-15T04:00:00Z",
  "expirationDate": "2028-09-14T04:00:00Z",
  "status": [
    "client delete prohibited",
    "client transfer prohibited",
    "client update prohibited",
    "server delete prohibited",
    "server transfer prohibited",
    "server update prohibited"
  ],
  "nameservers": [
    "ns1.google.com",
    "ns2.google.com",
    "ns3.google.com",
    "ns4.google.com"
  ]
}

This is genuine registry data fetched live and parsed — not a canned response. Add ?raw=1 to a domain query to get the unparsed WHOIS text instead.

A real domain lookup through the API returning genuine RDAP registry data for google.com

Step 7 — IP and ASN lookups, and the front-door allowlist

The same endpoint auto-detects IPv4/IPv6 addresses, CIDR prefixes and AS numbers, and the typed paths /api/ip/... and /api/autnum/... are available too:

curl -s http://127.0.0.1/api/8.8.8.8 | jq '{objectClassName,handle,name,startAddress,endAddress,cidr}'
curl -s http://127.0.0.1/api/AS15169 | jq '{objectClassName,handle,name}'
{
  "objectClassName": "ip network",
  "handle": "NET-8-8-8-0-2",
  "name": "GOGL",
  "startAddress": "8.8.8.0",
  "endAddress": "8.8.8.255",
  "cidr": "8.8.8.0/24"
}
{
  "objectClassName": "autnum",
  "handle": "AS15169",
  "name": "GOOGLE"
}

The nginx front door only publishes the UI, /api/ and /health. The operational and AI-integration endpoints the binary also exposes (/metrics, /info, /mcp, /openapi.json) and the bulk /batch endpoint are not reachable from the internet — they return 404 at the front door and are available only on-box via 127.0.0.1:8043:

for p in /metrics /info /mcp /openapi.json /batch; do
  echo "$p -> $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1$p)"
done
/metrics -> 404
/info -> 404
/mcp -> 404
/openapi.json -> 404
/batch -> 404

An IP lookup returning an RDAP network object, and the front-door allowlist returning 404 for the operational, MCP and batch endpoints

The same lookup works for any domain — here a second example rendered in the web UI:

The web UI rendering the registry data for github.com

Step 8 — Per-VM instance info and hardening

Every VM writes its own info file at first boot with the URLs for this instance and a summary of the hardening in the image:

sudo cat /root/whois-api-server-info.txt

The per-VM instance info file: the lookup URLs and the secure-by-default hardening summary

Why this is safe to expose as a public lookup: the core lookup is read-only and carries no credential, so there is nothing to compromise. The abuse surface is closed by design — the app port is fenced to loopback, /batch is disabled, the ?refresh cache-bypass is refused while the instance is open (so nobody can force the proxy to hammer registries), the upstream concurrency limit caps how many queries the proxy makes at once, and nginx rate-limits the /api/ path per client IP so it cannot be used as an unbounded open relay. In-memory caching means repeated queries are answered without touching upstream at all.

Step 9 — Require an API key (optional lock-down)

If you want the lookup itself restricted to authenticated clients, set one or more API keys. Every endpoint except /health and /ready then requires an Authorization: Bearer <key> or X-API-Key: <key> header, and clients with a key may also use ?refresh and per-key rate limits. Drop in a systemd override and restart:

sudo mkdir -p /etc/systemd/system/whois-api-server.service.d
sudo tee /etc/systemd/system/whois-api-server.service.d/apikey.conf >/dev/null <<'EOF'
[Service]
Environment=WHOIS_AUTH_KEYS=<your-token>
EOF
sudo systemctl daemon-reload && sudo systemctl restart whois-api-server

You can also edit auth.keys (and enable batch) directly in /etc/whois/config.yaml. See the upstream configuration reference for per-key names and rate limits.

Server Components

  • whois-api-server 1.2.0 — the WHOIS/RDAP query API (single native Go binary), /opt/whois-api-server/whois-server, run as the whois service account.
  • nginx — reverse proxy and front door on :80, serving the lookup UI and proxying /api/ and /health to the loopback API, with server_tokens off and a per-IP limit_req rate limit.
  • nftables — a loopback fence (/etc/nftables.conf) dropping non-loopback inbound to tcp/8043.
  • cloudimg lookup web UI — a self-contained HTML/JS page served from /var/lib/whois-api-server/viewer.

Filesystem Layout

  • /opt/whois-api-server/whois-server — the binary
  • /opt/whois-api-server/LICENSE, /opt/whois-api-server/COMPONENTS.txt — upstream MIT licence and component notice
  • /etc/whois/config.yaml — the hardened configuration (working directory of the unit)
  • /var/lib/whois-api-server/viewer/index.html — the lookup web UI
  • /etc/nftables.conf — the app-port loopback fence
  • /root/whois-api-server-info.txt — the per-VM instance info written at first boot

Managing the service

systemctl status whois-api-server --no-pager
sudo journalctl -u whois-api-server -n 50 --no-pager

Restart the API after you change its configuration with sudo systemctl restart whois-api-server.

Security

The API runs as a non-root service account. The app port is fenced to loopback with nftables and nginx is the only public surface, with a strict path allowlist so the operational, MCP and batch endpoints are never exposed to the internet. The lookup is public by design and ships no credential; set WHOIS_AUTH_KEYS (Step 9) to require an API key on every endpoint. nginx rate-limits /api/ per client IP and the binary caps concurrent upstream queries, so the proxy cannot be abused as an unbounded open relay. Keep the OS patched — unattended security updates are enabled in the image.

Troubleshooting

  • A lookup times out or returns an upstream error — the VM needs outbound access to the WHOIS (43/tcp) and RDAP (443/tcp) servers. Check the NSG and any egress firewall.
  • /api/... returns 502 — the API is not running; check systemctl status whois-api-server and sudo journalctl -u whois-api-server. The unit is gated on the first-boot marker /var/lib/cloudimg/whois-api-server-firstboot.done.
  • /api/... returns 401 after you set a key — the instance now requires an API key; send Authorization: Bearer <key> (or unset WHOIS_AUTH_KEYS to reopen it).
  • /api/... returns 429 — the per-IP rate limit engaged; tune limit_req in /etc/nginx/conf.d/cloudimg-whois-api-server-hardening.conf for your traffic.

Support

cloudimg images include 24/7 support. Contact support@cloudimg.co.uk with the offer name and your VM details.