Skip to content

Configuration ​

Configuration is read from the TOML file given with --config or the RP_CONFIG environment variable, else from /etc/redirect-proxy/config.toml when it exists (where the packages install it: sudo drops RP_CONFIG), else defaults are used. Commands that change users, API clients or rules print the data directory they change. config.example.toml documents every option. Each option can be overridden with an environment variable named RP_<SECTION>_<KEY>; lists are comma separated. A number, a boolean (true/false, yes/no, on/off, 1/0) or a log level that does not parse stops the start with an error naming the variable.

Secrets can be read from a file instead, so that they do not show in the process environment (docker inspect, /proc/<pid>/environ): set RP_ADMIN_SECRET_KEY_BASE_FILE, RP_ADMIN_METRICS_TOKEN_FILE, RP_AUTH_OIDC_CLIENT_SECRET_FILE or RP_BOOTSTRAP_ADMIN_PASSWORD_FILE to the path of a file holding the value (Docker and Kubernetes secrets). A trailing line break is ignored.

VariableDefault
RP_SERVER_LISTEN / RP_SERVER_PORT0.0.0.0 / 8080
RP_SERVER_CACHE_MAX_AGE3600
RP_SERVER_SHUTDOWN_GRACE5 (seconds)
RP_SERVER_TRAILING_SLASHstrict (ignore: also match with/without the trailing slash, host by host)
RP_SERVER_FALLBACK_URL(none)
RP_SERVER_NOT_FOUND_BODYNot Found (body of the 404 when no fallback is set)
RP_SERVER_HEALTH_PATH(none: off)
RP_SERVER_METRICS_PATH(none: off; needs RP_ADMIN_METRICS_TOKEN)
RP_SERVER_MAX_PATH_BYTES8192 (longer paths get a 414)
RP_SERVER_MAX_REQUEST_BODY_BYTES0 (forwarded requests with a longer body get a 413; 0: no limit)
RP_SERVER_MAX_CONNECTIONS_PER_IP256 (open connections per client address, an IPv6 /64, on the redirect listener; loopback peers and the trusted proxies are not limited; 0: no limit)
RP_SERVER_HTTPS_REDIRECTfalse (redirect plain HTTP requests to HTTPS before any rule)
RP_SERVER_UPSTREAM(none: off; an http:// or https:// origin that requests no rule answers are forwarded to, see In front of a website)
RP_SERVER_UPSTREAM_TIMEOUT60 (seconds to wait for the upstream's response head; idle timeout of upstream reads and writes)
RP_SERVER_UPSTREAM_TLS_VERIFYtrue (check an https upstream's certificate against the system CAs)
RP_ADMIN_ENABLEDtrue
RP_ADMIN_LISTEN / RP_ADMIN_PORT127.0.0.1 / 8081
RP_ADMIN_PUBLIC_URLhttp://localhost:8081
RP_ADMIN_SECRET_KEY_BASEgenerated into <data_dir>/secret_key_base
RP_ADMIN_SESSION_TTL28800 (8 hours)
RP_ADMIN_SESSION_IDLE_TIMEOUT3600 (1 hour, 0: never)
RP_ADMIN_METRICS_TOKEN(none: /metrics is public on a loopback listener, 404 on any other)
RP_STORAGE_DATA_DIR / RP_STORAGE_BACKUPS./data / 10
RP_STORAGE_WATCH_INTERVAL0 (seconds between checks of the rules file, 0: off)
RP_STORAGE_AUDIT_MAX_BYTES / RP_STORAGE_AUDIT_FILES10485760 (10 MB, 0: never rotate) / 5 rotated files kept
RP_AUTH_LOCAL_ENABLEDtrue
RP_AUTH_OIDC_ENABLED, _ISSUER, _CLIENT_ID, _CLIENT_SECRET, _ALLOWED_EMAILS, _ALLOWED_DOMAINS, _ALLOWED_GROUPS, _ALLOW_ANY_ACCOUNTdisabled
RP_AUTH_OIDC_SCOPESopenid,email,profile (must include openid)
RP_AUTH_OIDC_GROUPS_CLAIM, _BUTTON_LABEL, _LOGOUT_AT_PROVIDERgroups, Sign in with SSO, false
RP_AUTH_OIDC_ADMIN_GROUPS, RP_AUTH_OIDC_EDITOR_GROUPS, RP_AUTH_OIDC_VIEWER_GROUPS, RP_AUTH_OIDC_DEFAULT_ROLE(none), viewer (see Roles)
RP_AUTH_HEADER_ENABLED, _TRUSTED_PROXIESfalse, 127.0.0.1/32,::1/128
RP_AUTH_HEADER_USER_HEADER, _EMAIL_HEADERX-Forwarded-User, X-Forwarded-Email (empty user header: identify by email)
RP_AUTH_HEADER_ROLE_HEADER, RP_AUTH_HEADER_DEFAULT_ROLE(none), viewer (see Roles)
RP_API_ENABLED, RP_API_TOKEN_TTL, RP_API_MAX_BODY_BYTES, RP_API_REQUIRE_IF_MATCHtrue, 3600, 10485760, false
RP_API_LISTEN / RP_API_PORT, RP_API_PUBLIC_URLadmin.listen / admin.port, (none)
RP_API_OAUTH_ENABLED, _ISSUERdisabled
RP_API_OAUTH_AUDIENCE, _SCOPE_CLAIM, _REQUIRE_AT_JWTredirect-proxy, scope, true
RP_API_OAUTH_READ_SCOPE, _WRITE_SCOPE, _OPS_SCOPE, _AUDIT_SCOPEredirects:read, redirects:write, redirects:ops, audit:read
RP_ACCESS_LOG_ENABLED, _FORMAT, _ESCAPE, _OUTPUTtrue, combined, default, stdout
RP_ACCESS_LOG_ANONYMIZE_IP, _LOG_QUERY, _TRUSTED_PROXIEStruncate, false, (none)
RP_ACCESS_LOG_MAX_BYTES, _MAX_FILES, _COMPRESS0 (no rotation), 7, true
RP_ACCESS_LOG_{REDIRECT,ADMIN,API}_ENABLED, _FORMAT, _OUTPUTtrue, inherited, inherited
RP_BOOTSTRAP_ADMIN_USERNAME / _PASSWORDcreates the first local administrator when none exists
RP_LOG_LEVELinfo (debug, warning, error); does not affect the access log
RP_LOG_FORMATtext (json: one {time, level, msg, domain} object per line)
NO_COLORplain log lines (automatic when output is not a terminal)

Example file ​

config.example.toml documents every option, with its default:

toml
# redirect-proxy configuration.
#
# Every setting can be overridden by an environment variable named
# RP_<SECTION>_<KEY> in upper case, e.g. RP_SERVER_PORT=80 or
# RP_AUTH_OIDC_CLIENT_SECRET=... (lists are comma separated).

[server]
# Public redirect listener.
listen = "0.0.0.0"
port = 8080
# Cache-Control max-age (seconds) sent with permanent redirects (301 / 308).
# Temporary redirects (302 / 303 / 307) are always sent with no-store.
cache_max_age = 3600
# When set, unmatched requests are redirected (302) here instead of a 404.
fallback_url = ""
not_found_body = "Not Found"
# On SIGTERM, seconds during which /readyz answers 503 and connections are
# closed after each response, before the node stops (0 to 60).
shutdown_grace = 5
# "strict": /docs/ and /docs are different paths. "ignore": each host's
# rules are also tried with the trailing slash added or removed, before the
# rules of the next host (*.parent, then *).
trailing_slash = "strict"
# A path answered by this listener before any rule, for load balancers that
# can only probe the traffic port: 200 while serving, 503 while draining
# before a stop. Empty: off (every path goes to the rules).
health_path = ""
# A path where this listener serves the Prometheus metrics before any rule,
# for replicas without an admin or API listener. This listener is public:
# it needs admin.metrics_token, sent as "Authorization: Bearer <token>".
# Empty: off.
metrics_path = ""
# Requests with a longer path (in bytes) get a 414 without rule lookup.
max_path_bytes = 8192
# Requests forwarded to the upstream with a longer body (in bytes) get a 413
# (0: no limit, as the upstream's own limit applies).
max_request_body_bytes = 0
# Open connections one client address (IPv6: one /64) may hold on this
# listener: more are closed at once. Loopback peers,
# access_log.trusted_proxies and auth.header.trusted_proxies are not
# limited: list your load balancer there, or every client behind it shares
# its address (0: no limit).
max_connections_per_ip = 256
# Redirect plain HTTP requests to the same URL over HTTPS (301, or 308 for
# methods other than GET and HEAD), before any rule. Requests that a TLS
# proxy marks with X-Forwarded-Proto: https go to the rules (only from
# access_log.trusted_proxies when that list is set).
https_redirect = false
# The website behind this listener: requests no rule answers (whatever
# their method) are forwarded to it, as a reverse proxy, instead of getting
# a 404. An http:// or https:// origin: scheme, host and optional port, no
# path. Empty: off. Cannot be combined with fallback_url.
upstream = ""
# Seconds to wait for the upstream's response head, and the idle timeout
# of every write to and read from the upstream while a request is
# forwarded (and of upgraded connections, such as WebSockets). The connect
# timeout is 5 seconds. A client sending a request body may pause 10
# seconds between packets, then gets 408.
upstream_timeout = 60
# For an https:// upstream: check its certificate (system CAs, host name).
# false for an internal upstream with a self-signed certificate.
upstream_tls_verify = true

[access_log]
# Traffic logs, one line per request, of the redirect, admin and API
# listeners. The settings below are shared; [access_log.redirect],
# [access_log.admin] and [access_log.api] can override enabled, format and
# output per listener.
# Master switch for every listener:
enabled = true
# Default format. A preset or a template:
#   combined  nginx/Apache Combined Log Format (GoAccess, AWStats, Logstash
#             COMBINEDAPACHELOG, Fluent Bit "apache2", Datadog, Loki...)
#   common    NCSA Common Log Format
#   json      one JSON object per line (Elasticsearch, Loki, Vector, Splunk...)
# A template mixes text and nginx-style variables ($var or ${var}), e.g.
#   format = '$remote_addr "$request" $status $request_time $rule_id'
# Variables: remote_addr remote_user time_local time_iso8601 msec request
# request_method request_uri uri args server_protocol status body_bytes_sent
# request_time host http_referer http_user_agent location rule_id listener,
# plus http_<header> for any request header (e.g. $http_x_request_id).
# $http_authorization, $http_proxy_authorization and $http_cookie are refused:
# they would write credentials to the log.
format = "combined"
# Escaping of values in templates: "default" (nginx: \xHH), "json" or "none".
escape = "default"
# Default output: "stdout", "stderr" or a file path, absolute or relative to
# the working directory (e.g. "/var/log/redirect-proxy/access.log"). Missing
# directories are created; a renamed file (logrotate) is reopened.
output = "stdout"
# GDPR: client addresses are truncated by default (IPv4 /24, IPv6 /48).
# "none" logs full addresses.
anonymize_ip = "truncate"
# GDPR: query strings often carry personal data (e-mails, tokens, tracking
# ids). By default they are left out of the request, the referer and the
# redirect location.
log_query = false
# Take the client address from X-Forwarded-For / X-Real-IP only when the TCP
# peer is one of these proxies (like nginx real_ip). Empty: the peer address.
# The same address is used to throttle admin logins and API token requests.
trusted_proxies = []
# Built-in size based rotation of file output: rotate at max_bytes (0: never,
# leave it to logrotate; else at least 65536), keep max_files (1 to 100)
# rotated files, gzip them. Not together with logrotate on the same file.
max_bytes = 0
max_files = 7
compress = true

[access_log.redirect]
enabled = true
# Empty: use access_log.format / access_log.output.
format = ""
output = ""

[access_log.admin]
# Admin UI requests (including /healthz and /metrics probes). When the API
# shares the admin port, /api and /oauth requests use [access_log.api].
enabled = true
format = ""
output = ""

[access_log.api]
enabled = true
format = ""
output = ""

[admin]
enabled = true
# Keep the administration on a private interface (or behind a reverse proxy).
listen = "127.0.0.1"
port = 8081
# Externally visible URL of the admin, used to build the OIDC redirect URI.
public_url = "http://localhost:8081"
# Secret used to sign session cookies (at least 64 characters). When empty a
# random secret is generated and stored in <data_dir>/secret_key_base.
secret_key_base = ""
# Bearer token required by /metrics (at least 16 characters). Empty: /metrics
# is public, like /healthz and /readyz, when the listener is on the loopback
# interface, and not served (404) on any other address.
metrics_token = ""
# Session lifetime in seconds.
session_ttl = 28800
# A session unused for this many seconds ends (0: never).
session_idle_timeout = 3600

[log]
# The application log (the access log is configured below). Level: debug,
# info, warning, error, critical or none. Format: "text", or "json" for one
# JSON object per line ({time, level, msg, domain}).
level = "info"
format = "text"

[storage]
# Holds redirects.json, users.json, audit.jsonl, backups/ and the cookie secret.
data_dir = "./data"
# Number of previous redirects.json versions kept in data_dir/backups.
backups = 10
# Seconds between checks of redirects.json for changes made outside the
# server (ConfigMap, GitOps, an editor). A change is reloaded like SIGHUP.
# 0: off; at most 86400.
watch_interval = 0
# audit.jsonl is rotated to audit.jsonl.1 (then .2...) once it reaches this
# size in bytes (0: never), keeping audit_files rotated files.
audit_max_bytes = 10485760
audit_files = 5

[auth.local]
# Username / password accounts stored in users.json.
enabled = true

[auth.oidc]
# OpenID Connect single sign-on (Keycloak, Entra ID, Google, Okta, Authentik...).
# Register <public_url>/auth/oidc/callback as redirect URI at the provider.
enabled = false
issuer = "https://idp.example.com/realms/main"
client_id = "redirect-proxy"
client_secret = ""
scopes = ["openid", "email", "profile"]
# Who may sign in: a user must match at least one entry. E-mails and
# domains only match when the ID token has email_verified = true.
allowed_emails = []
allowed_domains = []
allowed_groups = []
# With all three lists empty the server does not start, unless this says
# that every account of the provider may sign in (a provider of your own;
# never a public one such as accounts.google.com).
allow_any_account = false
groups_claim = "groups"
button_label = "Sign in with SSO"
# On logout, also sign out at the provider and come back to the login page.
# Register <public_url>/login as a post-logout redirect URI at the provider.
logout_at_provider = false
# Roles: viewer (see rules, test URLs, export), editor (also change rules,
# import, reload) or admin (also users, API clients and the audit log). A
# user gets the highest role their groups (groups_claim) give, and
# default_role when they are in none of these groups.
admin_groups = []
editor_groups = []
viewer_groups = []
default_role = "viewer"

[auth.header]
# Trust an identity header set by an authenticating reverse proxy
# (oauth2-proxy, Authelia, Pomerium...). Only requests whose TCP peer address
# is in trusted_proxies are trusted: never expose the admin port directly
# when this is enabled.
enabled = false
# The identity header. Set user_header = "" to identify users by
# email_header instead; the two are never combined. A request carrying the
# header with several values is refused: make the proxy set, not append to,
# it.
user_header = "X-Forwarded-User"
email_header = "X-Forwarded-Email"
trusted_proxies = ["127.0.0.1/32", "::1/128"]
# A header the proxy sets to the user's role (viewer, editor or admin). Empty:
# every proxy user gets default_role. An unknown value gives viewer.
role_header = ""
default_role = "viewer"

[api]
# REST API under /api (bearer tokens) and OAuth 2.0 token endpoint
# (/oauth/token, client credentials grant).
# Manage API clients in the admin ("API clients") or with
# `redirect-proxy client add <name> --scope redirects:write`.
enabled = true
# The API and the admin UI share a listener when they use the same port
# (port = 0 means admin.port). Give the API another port to serve it on its
# own listener: the admin listener then no longer answers /api and /oauth,
# so each can be exposed to different networks. With the admin UI disabled
# the API keeps running on its own listener.
port = 0
# Address of the API's own listener. Empty: admin.listen. When the API shares
# admin.port it must be empty or equal to admin.listen.
listen = ""
# Externally visible URL of the API, shown in the admin usage examples.
# Defaults to admin.public_url (shared) or http://localhost:<port> (own port).
public_url = ""
# Lifetime of access tokens issued by /oauth/token (seconds, 60-86400).
token_ttl = 3600
# Maximum request body size (bulk imports).
max_body_bytes = 10485760
# Refuse PUT, PATCH, DELETE, bulk and import requests without an If-Match
# header (428 Precondition Required), so that no client overwrites a change
# it has not seen.
require_if_match = false

[api.oauth]
# Also accept JWT access tokens issued by an external OAuth 2.0 / OIDC
# authorization server (Keycloak, Entra ID, Auth0, Okta...). Tokens must be
# signed with a key of the issuer JWKS and contain iss, exp and an aud that
# includes `audience`.
enabled = false
issuer = "https://idp.example.com/realms/main"
audience = "redirect-proxy"
# Claim holding the scopes: a space separated string ("scope") or an array
# ("scp" is always read too).
scope_claim = "scope"
# Only accept tokens whose header says they are access tokens (typ at+jwt,
# RFC 9068): an ID token issued for the same audience is then refused even
# without a nonce. Turn it off only when your server cannot type its access
# tokens that way, and give the API an audience of its own.
require_at_jwt = true
# Names of the scopes at your authorization server.
read_scope = "redirects:read"
write_scope = "redirects:write"
ops_scope = "redirects:ops"
# GET /api/audit: sign-ins with client addresses, user and client changes.
audit_scope = "audit:read"

Released under the MIT License.