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/v1
kind: Role
metadata:
  name: nexus-gslb-redirect
  namespace: default          # must match httprouteNamespace
rules:
  - apiGroups: ["gateway.networking.k8s.io"]
    resources: ["httproutes"]
    verbs: ["get", "create", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: nexus-gslb-redirect
  namespace: default
subjects:
  - kind: ServiceAccount
    name: nexus-gslb          # service account Nexus runs as
    namespace: nexus-system
roleRef:
  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).

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

# Allow gslbd to write the config file
chown 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 model

RedirectRule

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.

API

List 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:8080old.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.cchttps://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?
© 2026