Files
remark42/site/content/docs/configuration/parameters/index.md
T
Dmitry VerkhoturovandGitHub fb7b6c2cdd Serve the build-independent web assets from the backend (#2181)
* Serve the build-independent web assets from the backend

`privacy.html`, `markdown-help.html` and the `400x400.jpeg` it embeds carry
no template variable, link no script or stylesheet, and are imported by
nothing in the widget. They now live in `backend/app/webassets/assets`,
embedded there, and are served under `/web` alongside the frontend build.

`/web` reads the frontend build first and falls back to them, which is what
lets an operator replace one by dropping a file into `--web-root`. That is
what `privacy.html` needs: it describes remark42.com, while the
authorization guide tells operators to hand its URL to Google and Facebook
as their own application's privacy policy.

Only a missing file falls through. An unreadable file in the web root keeps
reporting as unreadable rather than being silently replaced by the embedded
copy, and a name the filesystem rejects reports as missing rather than as a
server error, both matching what `http.Dir` did.

The dev server serves the same directory, so the Markdown help link in the
comment form resolves on the dev port as well as in production.

The two pages are served as they are written. `markdown-help.html` was
minified before, and its formatted inline stylesheet is most of its 8.5 kB;
that is 2.4 kB more over the wire, behind the hour-long cache header the
file server already sets.

Drops `copy-webpack-plugin`, which had no other pattern, and the stylelint
entries that only ever matched these files.

* Make pnpm dev:app start again

The dev server has been failing to start on two counts, so the flow the
contributing guide documents does not run at all.

`webpack-cli` 4 drives `webpack-dev-server` 5 through the argument order
of an older major, handing it the compiler where it expects the options
object. It rejects that against its schema and exits, complaining about an
unknown `_assetEmittingPreviousFiles` property, which is a field of the
compiler. `webpack-cli` 7 is the release that declares
`webpack-dev-server` 5 as a peer.

Past that, `http-proxy-middleware` resolves to 4.1.1, which no longer
accepts the two-argument call `webpack-dev-server` makes, so the `/api`
and `/auth` proxies throw on startup. It is pulled in by the security
override for CVE-2025-32996, the only override in the file with no upper
bound: `>=2.0.10` matches every later major. Bounding it to the 2.x line
keeps the fix and the API `webpack-dev-server` calls.

With both in place `pnpm dev:app` serves the widget and the pages under
`/web` on port 9000.
2026-08-21 18:43:06 -05:00

32 KiB

title
title
Command-Line Interface parameters

Required parameters

Most of the parameters have sane defaults and don't require customization. There are only a few parameters the user has to define:

  1. SECRET - secret key, can be any long and hard-to-guess string
  2. REMARK_URL - URL pointing to your Remark42 server, i.e., https://demo.remark42.com
  3. At least one OAuth2 provider, either via AUTH_<PROVIDER>_CID + AUTH_<PROVIDER>_CSEC or via AUTH_CUSTOM_*

The minimal docker-compose.yml has to include all required parameters:

version: "2"

services:
  remark42:
    image: ghcr.io/umputun/remark42:latest
    restart: always
    container_name: "remark42"
    environment:
      - REMARK_URL=https://demo.remark42.com # URL pointing to your Remark42 server
      - SITE=YOUR_SITE_ID # site ID, same as used for `site_id`, see "Setup on your website"
      - SECRET=abcd-123456-xyz-$%^& # secret key
      - AUTH_ANON=true # enable anonymous commenting
      - AUTH_GITHUB_CID=12345667890 # OAuth2 client ID
      - AUTH_GITHUB_CSEC=abcdefg12345678 # OAuth2 client secret
    volumes:
      - ./var:/srv/var # persistent volume to store all Remark42 data

Complete parameters list

Command line Environment Default Description
url REMARK_URL URL to Remark42 server, required
secret SECRET the shared secret key used to sign JWT, should be a random, long, hard-to-guess string, required
site SITE remark site name(s), multi
store.type STORE_TYPE bolt type of storage, bolt or rpc
store.bolt.path STORE_BOLT_PATH ./var parent directory for the bolt files
store.bolt.timeout STORE_BOLT_TIMEOUT 30s boltdb access timeout
store.rpc.api STORE_RPC_API rpc extension api url
store.rpc.timeout STORE_RPC_TIMEOUT http timeout (default: 5s)
store.rpc.auth_user STORE_RPC_AUTH_USER basic auth user name
store.rpc.auth_passwd STORE_RPC_AUTH_PASSWD basic auth user password
admin.type ADMIN_TYPE shared type of admin store, shared or rpc
admin.rpc.api ADMIN_RPC_API rpc extension api url
admin.rpc.timeout ADMIN_RPC_TIMEOUT http timeout (default: 5s)
admin.rpc.auth_user ADMIN_RPC_AUTH_USER basic auth user name
admin.rpc.auth_passwd ADMIN_RPC_AUTH_PASSWD basic auth user password
admin.rpc.secret_per_site ADMIN_RPC_SECRET_PER_SITE enable JWT secret retrieval per aud, which is site_id in this case
admin.shared.id ADMIN_SHARED_ID admin IDs (list of user IDs), multi
admin.shared.email ADMIN_SHARED_EMAIL admin@${REMARK_URL} admin emails, multi
backup BACKUP_PATH ./var/backup backups location
max-back MAX_BACKUP_FILES 10 max backup files to keep
cache.type CACHE_TYPE mem type of cache, redis_pub_sub or mem or none
cache.redis_addr CACHE_REDIS_ADDR 127.0.0.1:6379 address of Redis PubSub instance, turn redis_pub_sub cache on for distributed cache
cache.max.items CACHE_MAX_ITEMS 1000 max number of cached items, 0 - unlimited
cache.max.value CACHE_MAX_VALUE 65536 max size of the cached value, 0 - unlimited
cache.max.size CACHE_MAX_SIZE 50000000 max size of all cached values, 0 - unlimited
avatar.type AVATAR_TYPE fs type of avatar storage, fs, bolt, or uri
avatar.fs.path AVATAR_FS_PATH ./var/avatars avatars location for fs store
avatar.bolt.file AVATAR_BOLT_FILE ./var/avatars.db avatars bolt file location
avatar.uri AVATAR_URI ./var/avatars avatars store URI
avatar.rsz-lmt AVATAR_RESIZE 0 (disabled) max image size for resizing avatars on save
image.type IMAGE_TYPE fs type of image storage, fs, bolt or rpc
image.fs.path IMAGE_FS_PATH ./var/pictures permanent location of images
image.fs.staging IMAGE_FS_STAGING ./var/pictures.staging staging location of images
image.fs.partitions IMAGE_FS_PARTITIONS 100 number of image partitions
image.bolt.file IMAGE_BOLT_FILE ./var/pictures.db images bolt file location
image.rpc.api IMAGE_RPC_API rpc extension api url
image.rpc.timeout IMAGE_RPC_TIMEOUT http timeout (default: 5s)
image.rpc.auth_user IMAGE_RPC_AUTH_USER basic auth user name
image.rpc.auth_passwd IMAGE_RPC_AUTH_PASSWD basic auth user password
image.max-size IMAGE_MAX_SIZE 5000000 max size of image file
image.resize-width IMAGE_RESIZE_WIDTH 2400 width of a resized image
image.resize-height IMAGE_RESIZE_HEIGHT 900 height of a resized image
auth.ttl.jwt AUTH_TTL_JWT 5m JWT TTL
auth.ttl.cookie AUTH_TTL_COOKIE 200h cookie TTL
auth.send-jwt-header AUTH_SEND_JWT_HEADER false send JWT as a header instead of a server-set cookie; with this enabled, frontend stores the JWT in a client-side cookie. See security considerations.
auth.same-site AUTH_SAME_SITE default set same site policy for cookies (default, none, lax or strict)
auth.apple.cid AUTH_APPLE_CID Apple client ID (App ID or Services ID)
auth.apple.tid AUTH_APPLE_TID Apple service ID
auth.apple.kid AUTH_APPLE_KID Apple Private key ID
auth.apple.private-key-filepath AUTH_APPLE_PRIVATE_KEY_FILEPATH /srv/var/apple.p8 Apple Private key file location
auth.google.cid AUTH_GOOGLE_CID Google OAuth client ID
auth.google.csec AUTH_GOOGLE_CSEC Google OAuth client secret
auth.facebook.cid AUTH_FACEBOOK_CID Facebook OAuth client ID
auth.facebook.csec AUTH_FACEBOOK_CSEC Facebook OAuth client secret
auth.microsoft.cid AUTH_MICROSOFT_CID Microsoft OAuth client ID
auth.microsoft.csec AUTH_MICROSOFT_CSEC Microsoft OAuth client secret
auth.microsoft.tenant AUTH_MICROSOFT_TENANT common Azure AD tenant ID, domain, or "common"
auth.github.cid AUTH_GITHUB_CID GitHub OAuth client ID
auth.github.csec AUTH_GITHUB_CSEC GitHub OAuth client secret
auth.patreon.cid AUTH_PATREON_CID Patreon OAuth Client ID
auth.patreon.csec AUTH_PATREON_CSEC Patreon OAuth Client Secret
auth.discord.cid AUTH_DISCORD_CID Discord OAuth Client ID
auth.discord.csec AUTH_DISCORD_CSEC Discord OAuth Client Secret
auth.custom.name AUTH_CUSTOM_NAME custom OAuth provider name (used in /auth/<name>/...)
auth.custom.cid AUTH_CUSTOM_CID custom OAuth client ID
auth.custom.csec AUTH_CUSTOM_CSEC custom OAuth client secret
auth.custom.auth-url AUTH_CUSTOM_AUTH_URL custom OAuth authorization endpoint
auth.custom.token-url AUTH_CUSTOM_TOKEN_URL custom OAuth token endpoint
auth.custom.info-url AUTH_CUSTOM_INFO_URL custom OAuth user info endpoint
auth.custom.scopes AUTH_CUSTOM_SCOPES none custom OAuth scopes, comma-separated
auth.custom.id-field AUTH_CUSTOM_ID_FIELD sub user info field used as unique id
auth.custom.name-field AUTH_CUSTOM_NAME_FIELD name user info field used as display name
auth.custom.picture-field AUTH_CUSTOM_PICTURE_FIELD picture user info field used as avatar URL
auth.custom.email-field AUTH_CUSTOM_EMAIL_FIELD email user info field used as email
auth.telegram AUTH_TELEGRAM false Enable Telegram auth (telegram.token must be present)
auth.yandex.cid AUTH_YANDEX_CID Yandex OAuth client ID
auth.yandex.csec AUTH_YANDEX_CSEC Yandex OAuth client secret
auth.dev AUTH_DEV false local OAuth2 server, development mode only
auth.anon AUTH_ANON false enable anonymous login
auth.email.enable AUTH_EMAIL_ENABLE false enable auth via email
auth.email.from AUTH_EMAIL_FROM email from (e.g. john.doe@example.com or "John Doe"<john.doe@example.com>)
auth.email.subj AUTH_EMAIL_SUBJ remark42 confirmation email subject
auth.email.content-type AUTH_EMAIL_CONTENT_TYPE text/html email content type
notify.users NOTIFY_USERS none type of user notifications (telegram, email), multi
notify.admins NOTIFY_ADMINS none type of admin notifications (telegram, slack, webhook and/or email), multi
notify.queue NOTIFY_QUEUE 100 size of notification queue
notify.telegram.chan NOTIFY_TELEGRAM_CHAN the ID of telegram channel for admin notifications
notify.slack.token NOTIFY_SLACK_TOKEN Slack token
notify.slack.chan NOTIFY_SLACK_CHAN general Slack channel for admin notifications
notify.webhook.url NOTIFY_WEBHOOK_URL Webhook notification URL for admin notifications
notify.webhook.template NOTIFY_WEBHOOK_TEMPLATE {"text": {{.Text | escapeJSONString}}} Webhook payload template (Go text/template)
notify.webhook.headers NOTIFY_WEBHOOK_HEADERS HTTP header in format Header1:Value1,Header2:Value2,...
notify.webhook.timeout NOTIFY_WEBHOOK_TIMEOUT 5s Webhook connection timeout
notify.email.from_address NOTIFY_EMAIL_FROM from email address (e.g. john.doe@example.com or "John Doe"<john.doe@example.com>)
notify.email.verification_subj NOTIFY_EMAIL_VERIFICATION_SUBJ Email verification verification message subject
telegram.token TELEGRAM_TOKEN Telegram token (used for auth and Telegram notifications)
telegram.timeout TELEGRAM_TIMEOUT 5s Telegram connection timeout
smtp.host SMTP_HOST SMTP host
smtp.port SMTP_PORT SMTP port
smtp.helo_host SMTP_HELO_HOST SMTP HELO/EHLO hostname
smtp.username SMTP_USERNAME SMTP user name
smtp.password SMTP_PASSWORD SMTP password
smtp.login_auth SMTP_LOGIN_AUTH false enable LOGIN auth instead of PLAIN
smtp.tls SMTP_TLS false enable TLS for SMTP
smtp.starttls SMTP_STARTTLS false enable StartTLS for SMTP
smtp.insecure_skip_verify SMTP_INSECURE_SKIP_VERIFY false skip certificate verification for SMTP
smtp.timeout SMTP_TIMEOUT 10s SMTP TCP connection timeout
ssl.type SSL_TYPE none none-HTTP, static-HTTPS, auto-HTTPS + le
ssl.port SSL_PORT 8443 port for HTTPS server
ssl.cert SSL_CERT path to the cert.pem file
ssl.key SSL_KEY path to the key.pem file
ssl.acme-location SSL_ACME_LOCATION ./var/acme dir where obtained le-certs will be stored
ssl.acme-email SSL_ACME_EMAIL admin email for receiving notifications from LE
max-comment MAX_COMMENT_SIZE 2048 comment's size limit
min-comment MIN_COMMENT_SIZE 0 comment's minimal size limit, 0 - unlimited
max-votes MAX_VOTES -1 votes limit per comment, -1 - unlimited
votes-ip VOTES_IP false restrict votes from the same IP
anon-vote ANON_VOTE false allow voting for anonymous users, require VOTES_IP to be enabled as well
votes-ip-time VOTES_IP_TIME 5m same IP vote restriction time, 0s - unlimited
low-score LOW_SCORE -5 low score threshold
critical-score CRITICAL_SCORE -10 critical score threshold
positive-score POSITIVE_SCORE false restricts comment's score to be only positive
restricted-words RESTRICTED_WORDS words banned in comments (can use *), multi
restricted-names RESTRICTED_NAMES names prohibited to use by the user, multi
edit-time EDIT_TIME 5m edit window; set to 0 to disable comment editing and staged image cleanup
admin-edit ADMIN_EDIT false unlimited edit for admins
read-age READONLY_AGE read-only age of comments, days
image-proxy.http2https IMAGE_PROXY_HTTP2HTTPS false enable HTTP->HTTPS proxy for images
image-proxy.cache-external IMAGE_PROXY_CACHE_EXTERNAL false enable caching external images to current image storage
emoji EMOJI false enable emoji support
simple-view SIMPLE_VIEW false minimized UI with basic info only
proxy-cors PROXY_CORS false disable internal CORS and delegate it to proxy
allowed-hosts ALLOWED_HOSTS enable all limit hosts/sources allowed to embed comments via CSP 'frame-ancestors'
address REMARK_ADDRESS all interfaces web server listening address
port REMARK_PORT 8080 web server port
web-root REMARK_WEB_ROOT ./web web server root, supplies the frontend when it exists
update-limit UPDATE_LIMIT 0.5 updates/sec limit
trusted-proxy TRUSTED_PROXY none (trust any) reverse-proxy networks (CIDR/IP, comma-separated) trusted to set the client IP; see Trusted proxies and client IP
subscribers-only SUBSCRIBERS_ONLY false enable commenting only for Patreon subscribers
disable-signature DISABLE_SIGNATURE false disable server signature in headers
disable-fancy-text-formatting DISABLE_FANCY_HTML_FORMATTING false disable fancy comments text formatting (replacement of quotes, dashes, fractions, etc)
admin-passwd ADMIN_PASSWD none (disabled) password for admin basic auth
dbg DEBUG false debug mode
  • command-line parameters are long-form --<key>=value, i.e., --site=https://demo.remark42.com
  • multi parameters separated by , in the environment or repeated with command-line keys, like --site=s1 --site=s2 ...
  • required parameters have to be presented in the environment or provided in the command-line

Custom OAuth2 integration

Custom OAuth2 integration currently supports only one custom provider at a time, and AUTH_CUSTOM_NAME must match ^[a-z0-9][a-z0-9_-]*$.

Security Considerations for auth.send-jwt-header

When auth.send-jwt-header=true is enabled:

  • Security Impact: JWT tokens are stored in client-accessible cookies that can be accessed by JavaScript
  • Vulnerability: This increases vulnerability to XSS attacks compared to server-set HttpOnly cookies
  • Implementation Mitigations:
    • SameSite=Strict cookies to prevent CSRF attacks
    • Secure flag automatically added on HTTPS connections
    • __Host- prefix added on HTTPS to prevent subdomain attacks
    • Double Submit Cookie pattern with XSRF token matching the JWT ID

This configuration should only be used when:

  1. You need cross-domain authentication support
  2. You understand and accept the increased XSS risk
  3. You have implemented strong XSS protections on your site

Trusted proxies and client IP

Remark42 keys per-IP rate limiting — and, when --votes-ip is enabled, vote de-duplication and the stored comment IP — on the client IP. When Remark42 runs behind a reverse proxy (nginx, Reproxy, Traefik, Cloudflare, an ALB, a k8s ingress, …) the TCP connection it sees comes from the proxy, not the visitor, so the proxy forwards the real client IP in a header and Remark42 reads it (priority: X-Real-IP, then CF-Connecting-IP, then X-Forwarded-For) to recover the real IP.

⚠️ Security note. Those headers can be set by any client. If Remark42 trusts them from everyone, a caller can send X-Real-IP: <anything> and rotate its apparent IP to bypass rate limiting and vote de-duplication. Use --trusted-proxy so Remark42 reads the forwarding headers only when the request actually arrives from your proxy.

--trusted-proxy / TRUSTED_PROXY takes a comma-separated list of networks (CIDR) or bare IPs. When set, the forwarding headers are honored only if the direct peer — the machine that opened the TCP connection to Remark42 — falls inside one of them; for a request from any other peer those headers are dropped and the real socket IP is used. When it is not set, the headers are trusted from any client: this preserves the historical behavior so existing deployments keep working, but leaves the bypass above open, and Remark42 prints a warning at startup. If Remark42 is reachable from the internet, set this.

Two conditions must both hold to be safe:

1. Trust the right peer. Point --trusted-proxy at the network your proxy connects from, and nothing wider.

2. Your proxy must set the IP itself. Trusting a proxy is not enough if the proxy relays what the client sent. Because Remark42 reads X-Real-IP first, the proxy must set X-Real-IP to the real connecting client and overwrite any client value. A proxy that only appends to X-Forwarded-For (Traefik, most cloud ALBs, k8s ingress, HAProxy defaults) or that derives X-Real-IP from a client-controlled X-Forwarded-For leaves the visitor in control of the reported IP even through a trusted proxy:

  • nginx — safe with the manual's proxy_set_header X-Real-IP $remote_addr; (overwrites any client value).
  • Reproxy — sets X-Real-IP, but derives it from an incoming X-Forwarded-For when present, so a directly-exposed Reproxy still lets a client choose it; front it with something that strips client X-Forwarded-For, or don't rely on it for per-IP controls.
  • Cloudflare — does not set X-Real-IP, and a client-supplied X-Real-IP wins over CF-Connecting-IP; add a Cloudflare Transform Rule that sets X-Real-IP from CF-Connecting-IP and clears any inbound X-Real-IP / X-Forwarded-For.

Recommended --trusted-proxy per deployment:

Deployment Direct peer Remark42 sees --trusted-proxy
nginx in the same Docker Compose (the nginx manual) the proxy container's Docker IP the Docker network, e.g. 172.16.0.0/12 (covers Docker's default bridge pool) — or pin your compose network's subnet
reverse proxy on the host / another machine the proxy's host IP that proxy's IP or CIDR, e.g. 10.0.0.5
Directly exposed, no proxy the real client IP already --trusted-proxy=127.0.0.1/32 — a value no client matches, so forwarding headers are always ignored and the real connecting IP is used

Examples:

# docker-compose: nginx on the same network
TRUSTED_PROXY=172.16.0.0/12

# a single upstream proxy
--trusted-proxy=10.0.0.5

# multiple ranges (v4 and v6)
TRUSTED_PROXY=10.0.0.0/8,fd00::/8

Caveats:

  • IPv6. CIDRs are matched by family — 172.16.0.0/12 (or 0.0.0.0/0) never matches an IPv6 peer. If your proxy reaches Remark42 over IPv6 (dual-stack / IPv6-enabled Docker), add the IPv6 network too, or every visitor collapses onto the proxy's IP and gets over-throttled.
  • Don't publish Remark42's own port when trusting a Docker range. If Remark42's port is exposed to the host, external traffic is SNAT'd to the Docker gateway (inside 172.16.0.0/12) and appears trusted — re-opening the bypass. Publish only the proxy.
  • 0.0.0.0/0 trusts everyone and re-opens the bypass; too narrow a range over-throttles real visitors. If unsure which network your proxy uses, check a Remark42 request log for the peer address it reports.

Deprecated parameters

The following list of command-line options is deprecated and might be removed in the next major release after the version they were deprecated. After the Remark42 version update, please check the startup log once for deprecation warning messages to avoid trouble with unrecognized command-line options in the future.

Deprecated options
Command line Replacement Environment Replacement Default Description Deprecation version
auth.email.template none AUTH_EMAIL_TEMPLATE none email_confirmation_login.html.tmpl custom email message template file 1.5.0
auth.email.host smtp.host AUTH_EMAIL_HOST SMTP_HOST smtp host 1.5.0
auth.email.port smtp.port AUTH_EMAIL_PORT SMTP_PORT smtp port 1.5.0
auth.email.user smtp.username AUTH_EMAIL_USER SMTP_USERNAME smtp user name 1.5.0
auth.email.passwd smtp.password AUTH_EMAIL_PASSWD SMTP_PASSWORD smtp password 1.5.0
auth.email.tls smtp.tls AUTH_EMAIL_TLS SMTP_TLS false enable TLS 1.5.0
auth.email.timeout smtp.timeout AUTH_EMAIL_TIMEOUT SMTP_TIMEOUT 10s smtp timeout 1.5.0
img-proxy image-proxy.http2https IMG_PROXY IMAGE_PROXY_HTTP2HTTPS false enable HTTP->HTTPS proxy for images 1.5.0
notify.type notify.admins, notify.users NOTIFY_TYPE NOTIFY_ADMINS, NOTIFY_USERS 1.9.0
notify.email.notify_admin notify.admins=email NOTIFY_EMAIL_ADMIN NOTIFY_ADMINS=email 1.9.0
notify.telegram.token telegram.token NOTIFY_TELEGRAM_TOKEN TELEGRAM_TOKEN Telegram token 1.9.0
notify.telegram.timeout telegram.timeout NOTIFY_TELEGRAM_TIMEOUT TELEGRAM_TIMEOUT Telegram timeout 1.9.0
auth.twitter.cid none AUTH_TWITTER_CID none Twitter Consumer API Key 1.14.0
auth.twitter.csec none AUTH_TWITTER_CSEC none Twitter Consumer API Secret key 1.14.0

Admin users

Admins/moderators should be defined in docker-compose.yml as a list of user IDs or passed in the command line.

environment:
  - ADMIN_SHARED_ID=github_ef0f706a79cc24b17bbbb374cd234a691a034128,github_dae9983158e9e5e127ef2b87a411ef13c891e9e5

To get a user ID just log in and click on your username or any other user you want to promote to admins. It will expand login info and show the full user ID.

Docker image

Two parameters allow customizing the Docker container on the system level:

  • APP_UID - sets UID to run Remark42 application in container (default=1001)
  • TIME_ZONE - sets time zone of Remark42 container, would be used only on the backend as comments shown with user's timezone in the web interface (default=America/Chicago)

See umputun/baseimage for more details.