DNS: HTTP Redirects Reference

Nexus GSLB includes a built-in HTTP redirect service comparable to NS1's Redirect Manager. It listens on a configurable port, matches incoming requests by Host header against a stored rule table, and responds with HTTP 301, 302, 307, or 308. Rules are managed via the REST API and take effect immediately on all cluster nodes without a restart.

License: HTTP redirects require the paid tier or above. The redirect listener is disabled at startup on free and unlicensed installations. Contact licensing@gslb.nexus to upgrade.


Overview

Property

Value

Listener

Configurable port (typically 80)

Match key

HTTP Host header (case-insensitive, port and trailing dot stripped)

Redirect types

301 Permanent, 302 Temporary, 307 Temporary (method-preserving), 308 Permanent (method-preserving)

Path handling

Optional path + query string preservation

Rule activation

Immediate — live handler updated on API create/delete

Cluster sync

Rules replicated via NATS state sync to all nodes


Configuration

redirect: enabled: true listenAddr: "0.0.0.0" port: 80

Field

Type

Default

Description

enabled

bool

false

Start the redirect listener. Must be true to serve any redirects.

listenAddr

string

0.0.0.0

Bind address for the redirect HTTP listener.

port

int

—

Port to listen on. Required when enabled: true.

The redirect listener is a plain HTTP server (no TLS). To serve HTTPS redirects, run the listener behind Caddy or nginx, which terminates TLS and proxies to the redirect port.


Proxy integrations

When Nexus runs behind a reverse proxy (Caddy, Traefik, nginx on bare metal; or any Gateway API-conformant controller in Kubernetes), the built-in redirect listener may not be reachable by external clients. Two integrations allow Nexus to push redirect rules directly into the proxy when rules are created or deleted.

Kubernetes Gateway API

Nexus creates and manages HTTPRoute resources in a Kubernetes cluster. Any conformant Gateway controller — Envoy Gateway, Istio, Traefik v3, Nginx Gateway Fabric, Cilium, Kong — will pick up the route and serve the redirect.

Each redirect rule produces one HTTPRoute named nexus-redirect-<id> in the configured namespace. The route attaches to the specified Gateway via parentRefs and uses a RequestRedirect filter to return the correct status code and destination.

redirect: enabled: true listenAddr: "0.0.0.0" port: 80 integrations: gatewayApi: enabled: true kubeconfig: "" # path to kubeconfig file; empty = in-cluster credentials gatewayName: "my-gateway" gatewayNamespace: "default" httprouteNamespace: "default"

Field

Type

Required

Description

enabled

bool

yes

Enable the integration.

kubeconfig

string

no

Path to a kubeconfig file. Leave empty when Nexus runs inside the cluster (uses in-cluster credentials).

gatewayName

string

yes

Name of the Gateway resource the HTTPRoute will attach to.

gatewayNamespace

string

yes

Namespace of the Gateway resource.

httprouteNamespace

string

yes

Namespace in which HTTPRoute objects are created.

Required RBAC — the service account or kubeconfig user must have create, get, update, and delete on httproutes.gateway.networking.k8s.io in httprouteNamespace. Example manifest:

apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: nexus-gslb-redirect namespace: default # must match httprouteNamespacerules: - apiGroups: ["gateway.networking.k8s.io"] resources: ["httproutes"] verbs: ["get", "create", "update", "delete"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: nexus-gslb-redirect namespace: defaultsubjects: - kind: ServiceAccount name: nexus-gslb # service account Nexus runs as namespace: nexus-systemroleRef: kind: Role name: nexus-gslb-redirect apiGroup: rbac.authorization.k8s.io

preservePath behaviour — when preservePath: true, no path modifier is set on the RequestRedirect filter so the upstream controller preserves the original path. When preservePath: false and the target URL contains a non-root path, a ReplaceFullPath modifier is set.


Caddy

Nexus manages redirect routes in a running Caddy instance via its admin API. Each rule is stored with a stable @id (nexus-redirect-<id>) so it can be updated or removed without affecting other routes Caddy may manage.

redirect: enabled: true listenAddr: "0.0.0.0" port: 80 integrations: caddy: enabled: true adminUrl: "http://localhost:2019" # default serverName: "srv0" # default

Field

Type

Default

Description

enabled

bool

—

Enable the integration.

adminUrl

string

http://localhost:2019

URL of the Caddy admin API. Must be reachable from the Nexus process.

serverName

string

srv0

Caddy server name in the config tree. Run curl localhost:2019/config/apps/http/servers to list available servers.

preservePath behaviour — when preservePath: true, the Location header uses Caddy's {http.request.uri} placeholder to append the original path and query. When preservePath: false, the target URL is used verbatim.

Note — the Caddy admin API must be accessible from the Nexus process. By default it binds to localhost:2019 and is not authenticated; consider enabling admin.enforce_origin and admin.origins in your Caddyfile if the admin API is exposed beyond localhost.


Nginx

Nexus generates a single nginx server {} block config file containing all active redirect rules, writes it to disk, and signals nginx to reload. This covers bare-metal and VM deployments where nginx is the TLS-terminating front-end.

redirect: enabled: true listenAddr: "0.0.0.0" port: 80 integrations: nginx: enabled: true configDir: "/etc/nginx/conf.d" # must be writable by the gslbd process configFile: "nexus-redirects.conf" # default; omit to use this value reloadCommand: "nginx -s reload" # default; override for systemd or sudo

Field

Type

Default

Description

enabled

bool

—

Enable the integration.

configDir

string

—

Required. Directory where the config file is written (e.g. /etc/nginx/conf.d). Must be writable by the gslbd process.

configFile

string

nexus-redirects.conf

Filename within configDir. The file is fully regenerated on every rule change.

reloadCommand

string

nginx -s reload

Shell command to reload nginx. Split on whitespace; not passed through a shell. Use systemctl reload nginx or sudo nginx -s reload as needed.

Generated config format:

# Generated by Nexus GSLB — do not edit manually
# Updated: 2025-01-01T00:00:00Z
 
server {
listen 80;
server_name old.example.com;
return 301 https://new.example.com$request_uri;
}

When preservePath: false, $request_uri is omitted and the target URL is used verbatim. When the rule list is empty, a comment-only file is written and nginx is still reloaded (to remove previously written rules).

Permissions — gslbd must be able to write configDir/configFile and execute the reload command. A common setup:

# Allow gslbd to write the config filechown gslbd:nginx /etc/nginx/conf.d/nexus-redirects.conf# Allow gslbd to reload nginx without a password (sudoers)echo "gslbd ALL=(root) NOPASSWD: /usr/sbin/nginx -s reload" > /etc/sudoers.d/nexus-nginx# Then set reloadCommand: "sudo nginx -s reload"

Cloudflare

Nexus manages Redirect Rules in one or more Cloudflare zones via the Ruleset Engine API. Each Nexus redirect rule is stored as a single entry in the zone's http_request_redirect ruleset, identified by the description nexus-redirect-<id>.

redirect: enabled: true listenAddr: "0.0.0.0" port: 80 integrations: cloudflare: enabled: true apiToken: "your-cloudflare-api-token" zones: - zoneId: "abc123def456" domains: - "example.com" - "example.net" - zoneId: "xyz789" domains: [] # catch-all: matches any source FQDN not claimed above

Field

Type

Required

Description

enabled

bool

yes

Enable the integration.

apiToken

string

yes

Cloudflare API token. Must have Zone → Rules → Edit permission on all configured zones.

zones[].zoneId

string

yes

Cloudflare zone ID (found in the zone's Overview page).

zones[].domains

[]string

no

Source FQDNs (or domain suffixes) that belong to this zone. A redirect for old.example.com matches a zone with domain example.com. Leave empty for a catch-all zone used when no other zone matches.

Zone matching — Nexus checks each zone's domains list in order and uses the first match. A zone with no domains configured is used as a fallback. If no zone matches a source FQDN, the integration logs a warning and skips the rule.

preservePath behaviour — when preservePath: false, the Cloudflare rule uses a static target URL. When preservePath: true, the rule uses a Cloudflare expression concat("<target>", http.request.uri) which preserves both path and query string.

Required API token permissions:

  • Zone → Rules → Edit (for each zone)

  • Zone → Zone → Read (for zone lookup, if using the API to discover zone IDs)

Create a scoped token at Cloudflare Dashboard → My Profile → API Tokens → Create Token.


Data modelRedirectRule

Field

Type

Description

id

string

UUID assigned on creation.

tenantId

string

Tenant that owns this rule.

sourceFqdn

string

The source hostname (e.g. old.gslb.cc). Matched against the HTTP Host header.

targetUrl

string

The full URL to redirect to (e.g. https://new.gslb.cc).

code

int

HTTP redirect status code. 301 (permanent), 302 (temporary), 307 (temporary, method-preserving), or 308 (permanent, method-preserving).

preservePath

bool

When true, the request path and query string are appended to targetUrl.

createdAt

int64

Unix timestamp of creation.


APIList redirect rules

GET /api/v1/redirects

Returns all redirect rules for the current tenant.

Response 200 OK: array of RedirectRule objects.

[ { "id": "a1b2c3...", "tenantId": "default", "sourceFqdn": "old.gslb.cc", "targetUrl": "https://new.gslb.cc", "code": 301, "preservePath": true, "createdAt": 1716100000 }]

Create a redirect rule

POST /api/v1/redirects

Request body:

Field

Type

Required

Default

Description

sourceFqdn

string

yes

—

Source hostname, e.g. old.gslb.cc. Trailing dot optional. Must be a valid DNS hostname (letters, digits, -, _, dot-separated labels).

targetUrl

string

yes

—

Destination URL, e.g. https://new.gslb.cc. Must be an absolute http/https URL. Values containing control characters or the delimiters ; { } # " ' \ or whitespace are rejected.

code

int

no

301

301, 302, 307, or 308. Any other value is silently normalised to 301.

preservePath

bool

no

true

Append the incoming path and query string to targetUrl.

Response 201 Created: the created RedirectRule object. 400 Bad Request if sourceFqdn or targetUrl fails validation.

Why the strict validation: both fields are rendered into generated proxy configuration (nginx server_name/return directives, Caddy/Cloudflare/Gateway objects). Rejecting delimiter and control characters at this write boundary prevents a rule from injecting directives into the generated config. The nginx renderer independently skips any rule with unsafe characters as defense-in-depth.

The rule is loaded into the live redirect handler on all cluster nodes immediately; no restart is required.

Example — permanent redirect with path preservation:

curl -X POST https://nexus-api.example.com/api/v1/redirects \ -H "Authorization: Bearer $GSLB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sourceFqdn": "old.gslb.cc", "targetUrl": "https://new.gslb.cc", "code": 301, "preservePath": true }'

Example — temporary redirect to a fixed URL (no path):

curl -X POST https://nexus-api.example.com/api/v1/redirects \ -H "Authorization: Bearer $GSLB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "sourceFqdn": "maintenance.gslb.cc", "targetUrl": "https://status.gslb.cc", "code": 302, "preservePath": false }'

Delete a redirect rule

DELETE /api/v1/redirects/{id}

Removes the rule from the database and from the live handler on all cluster nodes immediately.

Response 204 No Content.


Path and query preservation

When preservePath: true, the request URI (path + query string) is appended to the base targetUrl. The trailing slash on targetUrl is normalised to avoid double slashes.

Request

targetUrl

preservePath

Redirect destination

GET /

https://new.gslb.cc

true

https://new.gslb.cc

GET /docs/api

https://new.gslb.cc

true

https://new.gslb.cc/docs/api

GET /search?q=foo

https://new.gslb.cc

true

https://new.gslb.cc/search?q=foo

GET /anything

https://new.gslb.cc

false

https://new.gslb.cc

When preservePath: false, all requests to the source hostname redirect to targetUrl verbatim, regardless of the path.


Host matching

The incoming Host header is normalised before lookup:

  • Converted to lower case

  • Port stripped (e.g. old.gslb.cc:8080 → old.gslb.cc)

  • Trailing dot removed (e.g. old.gslb.cc. → old.gslb.cc)

The sourceFqdn stored in the rule is normalised the same way at write time, so rules stored with or without a trailing dot match correctly.

If no rule matches the Host header, the redirect server responds 404 Not Found.


Permissions

Permission

Grants

redirect:read

List redirect rules

redirect:write

Create and delete redirect rules

Default role assignments:

Role

Permissions

tenant_admin

redirect:read, redirect:write

operator

redirect:read, redirect:write

viewer

redirect:read


Cluster state sync

Redirect rules are included in the cluster state snapshot and replicated via NATS to all nodes when the snapshot is published. Additionally, create and delete API operations call SetRedirectManager / Delete on the local node's in-memory handler directly, so the change takes effect on that node without waiting for the next sync cycle.

When a new node joins and receives its initial snapshot, all current redirect rules are loaded into its handler before it starts serving traffic.


DNS wiring

The redirect service handles the HTTP layer only. To make old.gslb.cc resolve to the Nexus nodes, you must create a GSLB service or DNS record pointing that hostname at the nodes running the redirect listener.

Typical setup:

  1. Create a pool with the Nexus node IPs as members

  2. Create a service: old.gslb.cc → that pool

  3. Create a redirect rule: old.gslb.cc → https://new.gslb.cc

  4. Enable the redirect listener on port 80 in the config

HTTP traffic for old.gslb.cc hits the Nexus node, the redirect listener serves the 301, and the browser follows it to https://new.gslb.cc.


Troubleshooting

Redirect listener not starting

Check systemctl status gslbd and look for "redirect listener starting" in the logs. Common causes:

  • redirect.enabled is false (default)

  • Port 80 is already bound by Caddy, nginx, or another process — use a different port and proxy to it

Rule not taking effect after creation

The rule is applied to the local node's handler immediately on API create. If requests hit a different node and that node hasn't received the state sync yet, there may be a brief window where the rule is absent. Verify with:

curl -v http://<each-node-ip>/ -H "Host: old.gslb.cc"

404 Not Found from redirect server

No rule matches the Host header. Verify the sourceFqdn in the stored rule matches the Host header exactly (after normalisation). Check with:

curl -s https://nexus-api.example.com/api/v1/redirects \ -H "Authorization: Bearer $GSLB_API_KEY" | jq '.[].sourceFqdn'

Code references

  • internal/redirect/redirect.go: Handler, Rule, ServeHTTP, buildTarget

  • internal/redirect/integration.go: Integration interface

  • internal/redirect/integration_gateway.go: GatewayAPIIntegration

  • internal/redirect/integration_caddy.go: CaddyIntegration

  • internal/redirect/integration_nginx.go: NginxIntegration, renderNginxConfig

  • internal/redirect/integration_cloudflare.go: CloudflareIntegration

  • internal/storage (rqlite.go + storage_*.go): RedirectRule, CreateRedirectRule, ListRedirectRules, ListAllRedirectRules

  • internal/storage/tenant.go: tenant-scoped wrapper methods

  • internal/api/handlers_redirect.go: listRedirects, createRedirect, deleteRedirect

  • internal/api/rbac.go: PermRedirectRead, PermRedirectWrite

  • cmd/gslbd/setup_dns.go: startup wiring — integration init, DB hydration, SetRedirectManager


Was this article helpful?