Docs / Configuration

Configuration

GoPress uses TOML and keeps one runtime configuration per site. Multiple sites may share PostgreSQL when each uses a different table prefix.

File Discovery

The runtime path is resolved in this order:

-config <path>                  explicit flag, highest priority
sites/{first-host}/config.toml  first discovered site configuration
sites/default/config.toml       fallback path

config/config.toml is a source template, not a runtime fallback. Its jwt_secret is intentionally blank. The web installer writes a site-specific file such as sites/localhost/config.toml with 0600 permissions.

Complete Example

[site]
name = "My Website"
url = "https://example.com"
language = "en"
timezone = "UTC"
theme = "mono-journal"

[server]
host = "0.0.0.0"
port = 8080
mode = "release"
# trusted_proxies = ["127.0.0.1", "::1"]  # reverse proxies to trust; empty = loopback only

[pg]
user = "postgres"
password = "postgres"
hostname = "localhost"
port = "5432"
database = "my_website"
schema = "public"
table_prefix = "gp_"
version = "v0"
max_open_conns = 20
max_idle_conns = 10
conn_max_lifetime = "30m"

[redis]
host = "localhost"
port = 6379
password = ""
db = 0
# namespace = "my-site"     # optional cache namespace when sites share one Redis DB

[cms]
jwt_secret = ""             # required; installer generates a unique value
jwt_expire_hours = 24
upload_dir = "uploads"
upload_max_size_mb = 10

[mail]
driver = "go-mail"
enabled = false
host = "smtp.example.com"
port = 587
encryption = "starttls"     # starttls / ssl / none
username = "smtp-user"
mail_key = "smtp-password-or-app-key"
from_email = "no-reply@example.com"
from_name = "My Website"
reply_to = ""
timeout_seconds = 10

[install]
completed = true

Site

  • name and url provide the static SEO baseline. Runtime site_name and site_description options from System Settings override rendered metadata.
  • language is the default locale and multilingual fallback.
  • timezone accepts an IANA name such as UTC, Asia/Shanghai, or America/New_York, or Local. Publish-time input is interpreted in this timezone, stored in UTC, and converted back for admin and frontend output.
  • theme is the active theme slug.

Server

host and port form the HTTP listen address. Behind a reverse proxy on the same machine (such as Nginx), prefer host = "127.0.0.1" so only the proxy can reach the application port. Use release in production; debug enables verbose Gin logging, including the route table on every router rebuild, and is meant for local development.

trusted_proxies lists the IPs or CIDRs of reverse proxies whose X-Forwarded-For / X-Real-IP headers are used to identify the client. Every other request is attributed to its TCP peer address. When empty, only loopback (127.0.0.1, ::1) is trusted, which fits a reverse proxy running on the same host. Invalid entries are logged and fall back to loopback-only, so a typo never reopens spoofing.

The client IP drives the admin login limiter, REST API rate limiting, identity plugin limiters, audit logs, and analytics. Trusting the wrong peers lets clients forge their IP to bypass those limits, so:

  • Reverse proxy on the same host: keep the default. In Nginx, overwrite the forwarding headers with the connection address instead of appending to them:

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    
  • Reverse proxy on another host: add its private address, for example ["10.0.0.5"].

  • A CDN in front: also add the CDN's origin-pull ranges. GoPress walks X-Forwarded-For from right to left, skips trusted proxies, and uses the first untrusted address as the client IP.

PostgreSQL

table_prefix isolates core, plugin, and theme tables when sites share a database. Pool fields control maximum open/idle connections and connection lifetime; tune them to the PostgreSQL deployment. See Database Prefixes.

Redis

Redis is optional. Remove the section or leave it unavailable to use the in-process L1 cache only; core falls back without making the site unavailable.

namespace is optional. Every Redis cache key starts with gopress:<namespace>:, and admin cache clearing and page-cache invalidation delete only keys under that namespace. When it is empty, core derives a stable short hash (for example s1a2b3c4d5e6f) from the [pg] hostname, port, database, schema, and table_prefix, so sites with different data never share a namespace. Explicit values keep only letters, digits, ., -, and _, up to 64 characters.

Several sites can therefore share one Redis database: pages with the same path and language stay separate, and clearing one site's cache leaves the others intact. Set namespace explicitly if it must stay the same when database connection settings change, for example after moving the database host.

CMS and Security

  • jwt_secret signs admin sessions and API bearer tokens. It must be a unique random value for every site. The installer generates one; the server refuses to start when it is empty or equals the shipped legacy placeholder. Generate one manually with openssl rand -base64 32.
  • jwt_expire_hours controls token lifetime. Disabled users and role changes take effect on the next request because account state is revalidated against the database.
  • upload_dir is the media root; uploads are grouped by year and month.
  • upload_max_size_mb limits each uploaded file.

Core also enables these protections without extra configuration:

  • Admin cookies use HttpOnly and SameSite=Lax; Secure is added when the site URL uses HTTPS.
  • State-changing admin and plugin-admin requests enforce same-origin Origin/Referer checks.
  • Failed admin logins are rate-limited per source IP and recorded in the audit log.
  • Client IPs cannot be spoofed: X-Forwarded-For / X-Real-IP are honored only from [server].trusted_proxies (loopback by default), so direct clients cannot forge headers to evade the limits above.
  • Public user sessions are database-backed and revalidate the active account on every request. Disabling the account revokes the stored session and clears its cookie on the next request.
  • Uploaded SVG, HTML, and XML documents are served as attachments with a sandbox CSP to prevent script execution in the site origin.

Mail

Mail transport is site-scoped. The admin Mail Settings page writes this section while preserving 0600 file permissions; mail_key is never echoed back, and leaving its form field blank keeps the stored value.

driver accepts the default go-mail implementation or stdlib. enabled is the transport-level switch, independent of saved notification rules. For Gmail, use smtp.gmail.com, port 587, starttls, the Gmail address for both username and sender, and a Google App Password in mail_key.

Runtime Files and Multiple Sites

sites/
  localhost/
    config.toml
    public/            generated sitemap.xml and future public artifacts
  example.com/
    config.toml
uploads/
  YYYY/MM/             originals and generated media variants

Use -config sites/example.com/config.toml to choose a site explicitly. Sites may share one PostgreSQL database through different table prefixes and one Redis database through separate cache namespaces. Keep site configuration, credentials, uploads, and generated public artifacts out of public source control unless a deployment policy explicitly says otherwise.