Environment variables
This page lists every setting Goiabada’s two servers read, and what each one does.
Goiabada runs as two servers, the auth server (goiabada-authserver) and the admin console (goiabada-adminconsole), and you configure both with environment variables. Most variables also have a command-line flag, and the flag wins when you set both:
GOIABADA_AUTHSERVER_LOG_LEVEL=debug ./goiabada-authserver
./goiabada-authserver --authserver-log-level=debugYou rarely need to start from scratch: the setup wizard writes a working set for your deployment, keys included.
Every variable
Section titled “Every variable”Read by says which server reads a variable. A server ignores the variables it doesn’t read, so you can hand both servers the same environment.
Initial setup
Section titled “Initial setup”The auth server reads these when it starts on an empty database, to create the first administrator and the admin console’s client. See the first start.
| Variable | What it does |
|---|---|
GOIABADA_ADMIN_EMAILFlag: --admin-emailDefault: [email protected]Read by: auth server |
The first administrator’s email address. |
GOIABADA_ADMIN_PASSWORDFlag: --admin-passwordDefault: empty Read by: auth server |
The first administrator’s password. Required on the first start: at least 15 characters and at most 72 bytes. |
GOIABADA_APPNAMEFlag: --appnameDefault: GoiabadaRead by: auth server |
The name the UI shows. Change it later under Admin, General in the admin console. |
GOIABADA_ADMINCONSOLE_OAUTH_CLIENT_SECRETFlag: --adminconsole-oauth-client-secretDefault: empty Read by: both |
The admin console’s client secret. The admin console needs it to call the auth server’s API, and refuses to start when the auth server refuses it. The auth server reads it only on the first start, as the secret of the client it creates for the admin console. |
GOIABADA_AUTHSERVER_BOOTSTRAP_ENV_OUTFILEFlag: --authserver-bootstrap-env-outfileDefault: empty Read by: auth server |
A file for the two-step first start. The auth server writes the credentials it generated there, then exits. |
Database
Section titled “Database”Only the auth server talks to the database. The admin console reaches your data through the auth server’s API.
| Variable | What it does |
|---|---|
GOIABADA_DB_TYPEFlag: --db-typeDefault: sqliteRead by: auth server |
The database engine: sqlite, mysql, postgres or mssql. |
GOIABADA_DB_HOSTFlag: --db-hostDefault: localhostRead by: auth server |
The database host. An IPv6 address works with or without brackets. |
GOIABADA_DB_PORTFlag: --db-portDefault: 3306Read by: auth server |
The database port. |
GOIABADA_DB_NAMEFlag: --db-nameDefault: goiabadaRead by: auth server |
The database name. |
GOIABADA_DB_USERNAMEFlag: --db-usernameDefault: rootRead by: auth server |
The database user. |
GOIABADA_DB_PASSWORDFlag: --db-passwordDefault: empty Read by: auth server |
The database user’s password. |
GOIABADA_DB_TLS_MODEFlag: --db-tls-modeDefault: preferRead by: auth server |
How the auth server protects its connection to MySQL, PostgreSQL or SQL Server: disable, prefer, require, verify-ca or verify-full. Only verify-full checks that it reached your own database: Where the database sits compares the five. prefer checks no certificate, and encrypts when the server offers TLS; on SQL Server it encrypts the login, and the rest only when the server forces encryption. Left unset, it’s prefer, and every start writes a warning. SQLite ignores it. |
GOIABADA_DB_TLS_CA_FILEFlag: --db-tls-ca-fileDefault: empty Read by: auth server |
A PEM file of the authorities you trust to sign the database’s certificate. It’s read once at start, in verify-ca and verify-full only, and trusted in place of the system’s authorities. The auth server won’t start if the file can’t be read, holds no certificate, or is set with another mode. SQLite ignores it. |
GOIABADA_DB_TLS_CLIENT_CERT_FILEFlag: --db-tls-client-cert-fileDefault: empty Read by: auth server |
A PEM file of the client certificate the auth server presents to a database that authenticates by certificate. Name its key in GOIABADA_DB_TLS_CLIENT_KEY_FILE. It’s read once at start, and presented in require, verify-ca and verify-full on PostgreSQL and MySQL: SQL Server’s driver can’t present one. SQLite ignores it. See A client certificate. |
GOIABADA_DB_TLS_CLIENT_KEY_FILEFlag: --db-tls-client-key-fileDefault: empty Read by: auth server |
A PEM file of the client certificate’s private key, beside GOIABADA_DB_TLS_CLIENT_CERT_FILE. It must not be encrypted: the auth server reads no passphrase. |
GOIABADA_DB_TLS_SERVER_NAMEFlag: --db-tls-server-nameDefault: empty Read by: auth server |
The host name verify-full checks the database’s certificate against, in place of GOIABADA_DB_HOST, when the address dialed isn’t a name the certificate carries: an IP address, a pooler or a private endpoint. Set it only with verify-full, on MySQL, PostgreSQL or SQL Server. SQLite ignores it. |
GOIABADA_DB_CREATEFlag: --db-createDefault: trueRead by: auth server |
Create the database if it doesn’t exist. Set it to false when you create the database yourself. |
GOIABADA_DB_DSNFlag: --db-dsnDefault: file::memory:?cache=sharedRead by: auth server |
The SQLite connection string. The default keeps everything in memory, so it’s gone when the server stops. See SQLite. |
GOIABADA_DB_MAX_OPEN_CONNSFlag: --db-max-open-connsDefault: 20Read by: auth server |
The most connections open at once. At least 1. |
GOIABADA_DB_MAX_IDLE_CONNSFlag: --db-max-idle-connsDefault: 20Read by: auth server |
The most idle connections kept for reuse, from 0 up to the open limit. Unset, it follows GOIABADA_DB_MAX_OPEN_CONNS. |
GOIABADA_DB_CONN_MAX_LIFETIMEFlag: --db-conn-max-lifetimeDefault: 30mRead by: auth server |
How long a connection is reused before it’s replaced. 0 means no limit. |
GOIABADA_DB_CONN_MAX_IDLE_TIMEFlag: --db-conn-max-idle-timeDefault: 5mRead by: auth server |
How long a connection can sit idle before it’s closed. 0 means no limit. |
The last four size the connection pool.
URLs and listeners
Section titled “URLs and listeners”A base URL is the address browsers and clients use. A listener is where the server accepts connections on its own machine, which can be quite different behind a proxy.
| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_BASEURLFlag: --authserver-baseurlDefault: http://localhost:9090Read by: both |
The auth server’s public URL. The first start also makes it the issuer that tokens carry. An https:// URL marks the auth server’s cookies Secure. |
GOIABADA_AUTHSERVER_INTERNALBASEURLFlag: --authserver-internalbaseurlDefault: empty Read by: both |
Where the admin console calls the auth server’s API, such as http://goiabada-authserver:9090 inside a cluster. Empty, it uses the public URL. The auth server only reports it in its startup log. |
GOIABADA_ADMINCONSOLE_INTERNAL_CA_FILEFlag: --adminconsole-internal-ca-fileDefault: empty Read by: admin console |
A PEM file of the certificate authorities you trust to sign the auth server’s certificate, on the https:// URL the admin console calls it at. They’re trusted beside the system’s authorities, not in place of them. It’s read once, at start. |
GOIABADA_ADMINCONSOLE_BASEURLFlag: --adminconsole-baseurlDefault: http://localhost:9091Read by: both |
The admin console’s public URL. The auth server links to it in its emails and sends visitors of / there. An https:// URL marks the admin console’s cookies Secure. The auth server’s first start also registers it, and it followed by /auth/callback, as the admin console client’s redirect URIs. To change it later, first add the new URL, and it followed by /auth/callback, on the client’s Redirect URIs tab: https://, or http on loopback. |
GOIABADA_AUTHSERVER_LISTEN_HOST_HTTPFlag: --authserver-listen-host-httpDefault: 0.0.0.0Read by: auth server |
The address the auth server’s HTTP listener binds. Set it empty to turn that listener off. See listen addresses. |
GOIABADA_AUTHSERVER_LISTEN_PORT_HTTPFlag: --authserver-listen-port-httpDefault: 9090Read by: auth server |
The auth server’s HTTP port. |
GOIABADA_AUTHSERVER_LISTEN_HOST_HTTPSFlag: --authserver-listen-host-httpsDefault: 0.0.0.0Read by: auth server |
The address the auth server’s HTTPS listener binds. It listens only once the certificate and key files are set. |
GOIABADA_AUTHSERVER_LISTEN_PORT_HTTPSFlag: --authserver-listen-port-httpsDefault: 9443Read by: auth server |
The auth server’s HTTPS port. |
GOIABADA_AUTHSERVER_CERTFILEFlag: --authserver-certfileDefault: empty Read by: auth server |
The TLS certificate file for the auth server’s HTTPS listener. Behind a proxy that handles TLS, you don’t need it. |
GOIABADA_AUTHSERVER_KEYFILEFlag: --authserver-keyfileDefault: empty Read by: auth server |
The TLS private key file for the auth server’s HTTPS listener. |
GOIABADA_ADMINCONSOLE_LISTEN_HOST_HTTPFlag: --adminconsole-listen-host-httpDefault: 0.0.0.0Read by: admin console |
The address the admin console’s HTTP listener binds. Set it empty to turn that listener off. |
GOIABADA_ADMINCONSOLE_LISTEN_PORT_HTTPFlag: --adminconsole-listen-port-httpDefault: 9091Read by: admin console |
The admin console’s HTTP port. |
GOIABADA_ADMINCONSOLE_LISTEN_HOST_HTTPSFlag: --adminconsole-listen-host-httpsDefault: 0.0.0.0Read by: admin console |
The address the admin console’s HTTPS listener binds. It listens only once the certificate and key files are set. |
GOIABADA_ADMINCONSOLE_LISTEN_PORT_HTTPSFlag: --adminconsole-listen-port-httpsDefault: 9444Read by: admin console |
The admin console’s HTTPS port. |
GOIABADA_ADMINCONSOLE_CERTFILEFlag: --adminconsole-certfileDefault: empty Read by: admin console |
The TLS certificate file for the admin console’s HTTPS listener. |
GOIABADA_ADMINCONSOLE_KEYFILEFlag: --adminconsole-keyfileDefault: empty Read by: admin console |
The TLS private key file for the admin console’s HTTPS listener. |
Proxies
Section titled “Proxies”| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_TRUST_PROXY_HEADERSFlag: --authserver-trust-proxy-headersDefault: falseRead by: auth server |
Take the client’s IP address from X-Forwarded-For or X-Real-IP. Turn it on behind a reverse proxy, and leave it off without one. See client IP addresses. |
GOIABADA_AUTHSERVER_TRUSTED_PROXIESFlag: --authserver-trusted-proxiesDefault: empty Read by: auth server |
Your proxies’ addresses, as comma-separated IP addresses or CIDR ranges, such as 10.0.0.0/8,192.168.1.5. |
GOIABADA_ADMINCONSOLE_TRUST_PROXY_HEADERSFlag: --adminconsole-trust-proxy-headersDefault: falseRead by: admin console |
The same switch for the admin console, which records the address in its logs. |
GOIABADA_ADMINCONSOLE_TRUSTED_PROXIESFlag: --adminconsole-trusted-proxiesDefault: empty Read by: admin console |
The same list for the admin console. |
Each of these is random bytes written as hex. Generate them as Session keys and the data encryption key show. They have no flags, so they stay out of process listings.
| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_SESSION_AUTHENTICATION_KEYFlag: none Default: empty Read by: auth server |
Required. 64 bytes that seal the auth server’s sessions. |
GOIABADA_AUTHSERVER_SESSION_ENCRYPTION_KEYFlag: none Default: empty Read by: auth server |
Required. 32 bytes that encrypt the auth server’s sessions. |
GOIABADA_AUTHSERVER_SESSION_AUTHENTICATION_KEY_PREVIOUSFlag: none Default: empty Read by: auth server |
The old authentication key, set only while you rotate the session keys. |
GOIABADA_AUTHSERVER_SESSION_ENCRYPTION_KEY_PREVIOUSFlag: none Default: empty Read by: auth server |
The old encryption key, set only while you rotate the session keys. |
GOIABADA_ADMINCONSOLE_SESSION_AUTHENTICATION_KEYFlag: none Default: empty Read by: admin console |
Required. 64 bytes that seal the admin console’s sessions. |
GOIABADA_ADMINCONSOLE_SESSION_ENCRYPTION_KEYFlag: none Default: empty Read by: admin console |
Required. 32 bytes that encrypt the admin console’s sessions. |
GOIABADA_ADMINCONSOLE_SESSION_AUTHENTICATION_KEY_PREVIOUSFlag: none Default: empty Read by: admin console |
The old authentication key, set only while you rotate the session keys. |
GOIABADA_ADMINCONSOLE_SESSION_ENCRYPTION_KEY_PREVIOUSFlag: none Default: empty Read by: admin console |
The old encryption key, set only while you rotate the session keys. |
GOIABADA_AES_ENCRYPTION_KEYFlag: none Default: empty Read by: auth server |
Required. 32 bytes that encrypt secrets in the database. |
GOIABADA_AES_ENCRYPTION_KEY_PREVIOUSFlag: none Default: empty Read by: auth server |
The old data encryption key, set only while you rotate it. |
Logging
Section titled “Logging”Both servers write their logs to standard error.
| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_LOG_LEVELFlag: --authserver-log-levelDefault: infoRead by: auth server |
The lowest level written: debug, info, warn or error. debug adds a few diagnostic records; for one record per request, turn on GOIABADA_AUTHSERVER_LOG_HTTP_REQUESTS. |
GOIABADA_AUTHSERVER_LOG_FORMATFlag: --authserver-log-formatDefault: textRead by: auth server |
text for one key=value line per record, or json for one JSON object per record. |
GOIABADA_AUTHSERVER_LOG_HTTP_REQUESTSFlag: --authserver-log-http-requestsDefault: falseRead by: auth server |
Write one record per request, except health checks, static files and the favicon. Query values are redacted, apart from a short list of safe ones. |
GOIABADA_AUTHSERVER_LOG_SQLFlag: --authserver-log-sqlDefault: falseRead by: auth server |
Write one record per SQL statement, with its text but not the values it ran with. |
GOIABADA_AUTHSERVER_DEBUG_API_REQUESTSFlag: --authserver-debug-api-requestsDefault: falseRead by: auth server |
Write one record per API call, with its method, target, status, duration and both bodies. Credentials in the bodies and the Authorization header are redacted. |
GOIABADA_ADMINCONSOLE_LOG_LEVELFlag: --adminconsole-log-levelDefault: infoRead by: admin console |
The lowest level written: debug, info, warn or error. |
GOIABADA_ADMINCONSOLE_LOG_FORMATFlag: --adminconsole-log-formatDefault: textRead by: admin console |
text or json, as for the auth server. |
GOIABADA_ADMINCONSOLE_LOG_HTTP_REQUESTSFlag: --adminconsole-log-http-requestsDefault: falseRead by: admin console |
Write one record per request, as the auth server’s switch does. |
A level or format spelled any other way, uppercase included, stops the server at start. To set the time zone of the timestamps, see time zone.
Metrics
Section titled “Metrics”Each server can serve Prometheus metrics on a listener of its own, which answers GET /metrics and nothing else. It has no authentication, so never publish its port to the internet. Monitoring covers what it reports.
| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_METRICS_ENABLEDFlag: --authserver-metrics-enabledDefault: falseRead by: auth server |
Serve the auth server’s metrics listener. Off, nothing listens on its port. |
GOIABADA_AUTHSERVER_LISTEN_HOST_METRICSFlag: --authserver-listen-host-metricsDefault: 0.0.0.0Read by: auth server |
The address the auth server’s metrics listener binds. Use 127.0.0.1 when the scraper runs on the same host. |
GOIABADA_AUTHSERVER_LISTEN_PORT_METRICSFlag: --authserver-listen-port-metricsDefault: 9190Read by: auth server |
The auth server’s metrics port. |
GOIABADA_ADMINCONSOLE_METRICS_ENABLEDFlag: --adminconsole-metrics-enabledDefault: falseRead by: admin console |
Serve the admin console’s metrics listener. Off, nothing listens on its port. |
GOIABADA_ADMINCONSOLE_LISTEN_HOST_METRICSFlag: --adminconsole-listen-host-metricsDefault: 0.0.0.0Read by: admin console |
The address the admin console’s metrics listener binds. |
GOIABADA_ADMINCONSOLE_LISTEN_PORT_METRICSFlag: --adminconsole-listen-port-metricsDefault: 9191Read by: admin console |
The admin console’s metrics port. |
Customization
Section titled “Customization”| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_STATICDIRFlag: --authserver-staticdirDefault: empty Read by: auth server |
A directory to serve the auth server’s static files from, instead of the built-in ones. See Change the templates. |
GOIABADA_AUTHSERVER_TEMPLATEDIRFlag: --authserver-templatedirDefault: empty Read by: auth server |
A directory to load the auth server’s page templates from, instead of the built-in ones. |
GOIABADA_ADMINCONSOLE_STATICDIRFlag: --adminconsole-staticdirDefault: empty Read by: admin console |
The same, for the admin console’s static files. |
GOIABADA_ADMINCONSOLE_TEMPLATEDIRFlag: --adminconsole-templatedirDefault: empty Read by: admin console |
The same, for the admin console’s page templates. |
GOIABADA_I18N_OVERRIDES_DIRFlag: none Default: empty Read by: both |
A directory whose catalogs subfolder holds active.<language>.toml files, merged key by key over the built-in text. Without a catalogs subfolder nothing is loaded. Set it on both servers. See Add a language. |
GOIABADA_PROFILE_PICTURE_MAX_SIZE_BYTESFlag: none Default: 3145728Read by: auth server |
The largest profile picture or client logo the API accepts, in bytes. The default is 3 MiB. The admin console’s upload form stops at 3 MiB, whatever this says. |
Rate limiting
Section titled “Rate limiting”| Variable | What it does |
|---|---|
GOIABADA_AUTHSERVER_RATELIMITER_ENABLEDFlag: --authserver-ratelimiter-enabledDefault: falseRead by: auth server |
Turn on the rate limits below that are counted by an IP address. Those counted by a user or an email apply whether or not it’s on. The setup wizard asks whether you want them. |
Rate limits
Section titled “Rate limits”The auth server limits these endpoints. The limits counted by a user or an email always apply, and GOIABADA_AUTHSERVER_RATELIMITER_ENABLED adds the limits counted by an IP address: the Applies column says always or switch on. A refused request gets 429 Too Many Requests, in the format the endpoint usually answers with. The limiter is the name the rate_limit_exceeded audit event and the goiabada_rate_limit_refusals_total metric give it.
| Endpoint | Limit | Counts | Counted by | Applies |
|---|---|---|---|---|
POST /auth/pwd (password sign-in)Limiter: pwd_account_net |
10 per 15 minutes | failed sign-ins | IP address and email, shared with the password grant | switch on |
POST /auth/pwdLimiter: pwd_account |
100 per 60 minutes | failed sign-ins | email, shared with the password grant | always |
POST /auth/pwdLimiter: pwd_ip |
30 per minute | failed sign-ins | IP address | switch on |
POST /auth/pwdLimiter: pwd_ip_requests |
120 per minute | every request | IP address | switch on |
POST /auth/otp (authenticator code)Limiter: otp |
5 per 15 minutes | failed codes | user | always |
POST /api/v1/account/email/verificationLimiter: email_verification |
5 per 15 minutes | failed codes | the token’s user | always |
POST /api/v1/account/email/verification/sendLimiter: email_verification_send |
5 per 60 minutes | every request | the token’s user | always |
PUT /api/v1/account/password, PUT /api/v1/account/otp and PUT /api/v1/account/emailLimiter: account_password |
5 per 15 minutes, for the three together | failed passwords | the token’s user | always |
POST /forgot-passwordLimiter: forgot_pwd_email |
5 per 5 minutes | every request | always | |
POST /forgot-passwordLimiter: forgot_pwd_ip |
20 per 5 minutes | every request | IP address | switch on |
GET /reset-password and POST /reset-passwordLimiter: reset_pwd |
30 per 5 minutes, for the two together | every request | IP address | switch on |
POST /account/register (self-registration)Limiter: register_email |
5 per 5 minutes | every request | always | |
POST /account/registerLimiter: register |
20 per 5 minutes | every request | IP address | switch on |
GET /account/activate and POST /account/activateLimiter: activate |
30 per 5 minutes, for the two together | every request | IP address | switch on |
POST /connect/register (dynamic client registration)Limiter: dcr |
10 per minute | every request | IP address | switch on |
POST /auth/token with grant_type=passwordLimiter: pwd_account_net |
10 per 15 minutes | failed sign-ins | IP address and username, shared with POST /auth/pwd |
switch on |
POST /auth/token with grant_type=passwordLimiter: pwd_account |
100 per 60 minutes | failed sign-ins | username, shared with POST /auth/pwd |
always |
POST /auth/token with grant_type=passwordLimiter: ropc_ip |
30 per minute | failed sign-ins | IP address | switch on |
POST /auth/token with grant_type=passwordLimiter: ropc_ip_requests |
120 per minute | every request | IP address | switch on |
A few rules make these work:
- A limit on failures isn’t spent by a success. Signing in normally never uses it up, so many people signing in from one address, such as an office or a mobile network, never use up
pwd_iporropc_ip. A failed sign-in is a wrong password or an email or username that names no account, and through the password grant also a disabled user or one with two-factor authentication. - Every sign-in from one address still has a ceiling.
pwd_ip_requestsandropc_ip_requestscount every request, right password or wrong, at 120 a minute, far more than people signing in from one office reach. Each password check is a deliberately slow hash, so this bounds the work one address can make the server do. - Shared means one budget. A wrong password counts once against the same budget whether it arrives at the sign-in form or at the password grant.
- Emails and usernames are compared in lowercase, and IPv6 addresses are grouped by their
/64, so a client can’t step through its own network to reset a limit. - Limits on failures against an account or a user hold across replicas on PostgreSQL, MySQL and SQL Server, where they’re counted in the database, so a rollout doesn’t refill them either. If the database doesn’t answer the check within 5 seconds, the request gets a server error rather than going through. On SQLite, which runs one replica, they’re counted in the process.
- The other limits are per process, on every database:
pwd_ip,ropc_ipand every limit on every request. With three replicas, each one allows the full budget, and a restart resets it. For a limit that holds across your whole deployment, set one at your gateway, proxy or CDN, which also stops a flood before it reaches Goiabada. - A limit counted by IP address is only as good as the address. Behind a proxy, set up client IP addresses first.
- Anyone who knows an email can block its password sign-in. 100 wrong passwords in an hour block that account’s password sign-in, at the form and through the password grant, for up to an hour, even with the right password, and about 100 more an hour keep it blocked. A password reset doesn’t clear the count, and anyone already signed in stays signed in. That’s the price of a bound on guessing that holds whatever address the server sees. If it happens, block the attacker at your proxy or CDN, which sees their address.
Each limit that trips writes a rate_limit_exceeded audit event, once per key and window on each replica.
Values
Section titled “Values”The servers trim spaces around every value. An empty number or switch counts as unset and takes its default, but an empty text value is just empty: that’s how an empty listen host turns a listener off.
A number, switch or duration that doesn’t parse stops the server at start. It prints one line naming every bad value and exits with code 2, so a typo can’t leave a setting quietly on its default. A log level or log format other than the values listed, spelled in lowercase, stops it too, with unable to install the log handler and code 1. A switch is true or false, and a duration needs its unit, as in 30m, 1h or 90s.
Each server accepts only the flags of the variables it reads. Give the admin console a flag only the auth server reads, or the other way round, and it refuses to start.
Listen addresses
Section titled “Listen addresses”A listen host is an address on the server’s own machine, not its public URL. It decides which of the machine’s addresses accept connections.
0.0.0.0, the default, serves IPv4 and IPv6 alike.::means the same. On a host with IPv6 switched off, it serves IPv4 alone.- A specific address serves only its own family.
127.0.0.1takes no IPv6 connection, and::1no IPv4 one. A hostname such aslocalhostbinds only the first address it resolves to, so prefer a literal address. - Write an IPv6 address bare or in brackets.
::1and[::1]are the same address. A link-local address takes its zone, as infe80::1%eth0.
Each server needs at least one listener. With both off, it refuses to start.
Client IP addresses
Section titled “Client IP addresses”The rate limiter counts by client IP address, and the audit log records it. With TRUST_PROXY_HEADERS off, the server uses the address of whatever connected to it. Behind a proxy, that’s the proxy, so every client looks the same, and every user shares one rate limit: see Too many attempts or 429.
Turn TRUST_PROXY_HEADERS on behind a proxy. With TRUSTED_PROXIES empty, the server trusts one hop: the last address in X-Forwarded-For, or X-Real-IP when there’s none. That’s right behind a single proxy that sets or appends X-Forwarded-For. With more than one hop in front, such as a CDN and a proxy, list them in TRUSTED_PROXIES, and the server walks X-Forwarded-For from the right across them.
Once you set TRUSTED_PROXIES, it must include the proxy that connects to Goiabada: forwarded headers from any other address are ignored. An entry that’s neither an IP address nor a CIDR range stops the server at start, even with TRUST_PROXY_HEADERS off.
Session keys
Section titled “Session keys”Each server seals its sessions with its own pair of keys: a 64-byte authentication key and a 32-byte encryption key. Generate a fresh pair for each server, and for each deployment:
openssl rand -hex 64 # authentication keyopenssl rand -hex 32 # encryption keyNever reuse the example keys from a page or a sample file.
To rotate the keys without signing anyone out:
- Move each server’s current pair into its two
_PREVIOUSvariables. - Set the current variables to freshly generated keys.
- Restart both servers. They seal new sessions with the new pair and still open sessions sealed with the old one. On Kubernetes, where a rollout runs old and new pods side by side, this takes two rollouts instead: see below.
- Once the longest session lifetime has passed, remove the
_PREVIOUSvariables.
A sign-in still in progress when you remove them may have to start again. Skip the _PREVIOUS variables altogether and everyone simply signs in again once. Rotate secrets has the commands for each platform.
The data encryption key
Section titled “The data encryption key”GOIABADA_AES_ENCRYPTION_KEY encrypts the secrets the auth server stores: client secrets, the SMTP password, verification and password-reset codes, authenticator seeds, and the private keys that sign tokens. Only the auth server needs it.
openssl rand -hex 32To rotate it, set GOIABADA_AES_ENCRYPTION_KEY to a new key and GOIABADA_AES_ENCRYPTION_KEY_PREVIOUS to the old one, then restart the auth server. At start, it re-encrypts everything under the new key in one transaction. Leaving the previous key set across restarts is harmless, but remove it once the rotation is done. If the data decrypts under neither key, the auth server refuses to start rather than risk damaging it. Rotate secrets has the procedure for each platform, and Back up the AES key where to keep the key.
The first start
Section titled “The first start”When the auth server starts on an empty database, it creates the first administrator from GOIABADA_ADMIN_EMAIL and GOIABADA_ADMIN_PASSWORD, and a client for the admin console. How it gets that client’s secret depends on what you set:
GOIABADA_ADMINCONSOLE_OAUTH_CLIENT_SECRETset: it uses that secret and carries on running. Give both servers the same value. This is what the setup wizard writes.- Only
GOIABADA_AUTHSERVER_BOOTSTRAP_ENV_OUTFILEset: it generates the secret and both servers’ session keys, writes them to that file, and exits. Copy them into your configuration and start again. - Neither set: it refuses to start, and the database stays empty.
On every later start, the database already holds the administrator and the client, and none of these variables does anything on the auth server. Changed one and can’t sign in? See Locked out of the admin console.
The connection pool
Section titled “The connection pool”The four pool settings apply to PostgreSQL, MySQL and SQL Server. SQLite always uses one connection, though the values are still checked there. A value out of range stops the server at start, as does an idle limit above the open limit. A request that finds every connection busy waits for one.
The lifetime and idle time hand connections back over time, so they move to a new endpoint after a failover and a quiet replica doesn’t hold on to them. Every start writes one database connection pool log record with the pool it opened, and the migrate subcommand reads the same settings.
With several replicas, each opens its own pool against the one database. High availability shows how to size it against your database’s connection limit.
SQLite
Section titled “SQLite”For a SQLite file that survives restarts, point GOIABADA_DB_DSN at a file, with a busy timeout and write-ahead logging:
GOIABADA_DB_DSN='file:/var/lib/goiabada/goiabada.db?_pragma=busy_timeout=5000&_pragma=journal_mode=WAL'The busy timeout makes a request wait up to 5 seconds for a lock instead of failing at once. Write-ahead logging lets reads carry on while a write is in progress.
Time zone
Section titled “Time zone”Both servers also read TZ, the standard variable, with no GOIABADA_ prefix and no flag. It sets the zone of the log timestamps and of the times the servers format. Use an IANA name such as Europe/Lisbon, or an absolute path to a zone file, either one optionally after a leading :.
The servers carry their own zone database, so the host needs none, and TZ works on Windows too. Unset, the host’s zone is used, and empty means UTC. A name that’s no zone, Local, or a zone file that doesn’t load stops the server at start.