From 5eed14b3c789409287550cbff49b69b4f8e255a9 Mon Sep 17 00:00:00 2001 From: Swastik Tripathi Date: Tue, 10 Nov 2026 08:45:43 +0530 Subject: [PATCH 1/2] docs: document rate-limit and trusted-proxies options Co-Authored-By: Claude Opus 5.5 --- content/docs/configuration/environment-variables.mdx | 3 +++ content/docs/deploy/reverse-proxy.mdx | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/content/docs/configuration/environment-variables.mdx b/content/docs/configuration/environment-variables.mdx index 36b595d9..22945877 100644 --- a/content/docs/configuration/environment-variables.mdx +++ b/content/docs/configuration/environment-variables.mdx @@ -22,6 +22,8 @@ All flags and their corresponding environment variables: | `--webhook-private-network-allowlist` | `MEMOS_WEBHOOK_PRIVATE_NETWORK_ALLOWLIST` | empty | Allowed private destinations: exact hostnames, IP addresses, or CIDRs | | `--allow-private-webhooks` | `MEMOS_ALLOW_PRIVATE_WEBHOOKS` | `false` | Deprecated blanket exception for private webhook destinations | | `--log-level` | `MEMOS_LOG_LEVEL` | `info` | Log verbosity (`debug`, `info`, `warn`, `error`) | +| `--rate-limit` | `MEMOS_RATE_LIMIT` | `true` | Request rate limiting for sign-in, signup, link previews, uploads, and writes; `false` turns every limit off (26.10+) | +| `--trusted-proxies` | `MEMOS_TRUSTED_PROXIES` | `private` | Proxies whose forwarding headers identify the client address: IPs, CIDRs, `private` (loopback and private ranges), or `none`; comma-separated (26.10+). See [Reverse Proxy](/docs/deploy/reverse-proxy#client-addresses-and-rate-limiting) | | `--config-dir` | `MEMOS_CONFIG_DIR` | `/etc/memos/config` | Directory of [declarative configuration](/docs/configuration/declarative-configuration) files (26.10+) | | `--config-apply` | `MEMOS_CONFIG_APPLY` | `startup` | `startup` applies the configuration directory before serving; `off` leaves it to `memos config apply` (26.10+) | @@ -62,6 +64,7 @@ An explicit flag always wins over the environment variable for the same setting. - select private or public access explicitly in the `ACCESS` instance setting - set `MEMOS_INSTANCE_URL` to the canonical external URL when the app sits behind a proxy +- if the proxy is not on a loopback or private address, list it in `MEMOS_TRUSTED_PROXIES` so rate limits apply per client - prefer explicit `MEMOS_DRIVER` and `MEMOS_DSN` when you are not using SQLite defaults - allow only required private webhook destinations with `MEMOS_WEBHOOK_PRIVATE_NETWORK_ALLOWLIST`; leave the deprecated blanket option disabled - keep secrets such as database credentials outside committed compose files diff --git a/content/docs/deploy/reverse-proxy.mdx b/content/docs/deploy/reverse-proxy.mdx index ca780f51..54a52047 100644 --- a/content/docs/deploy/reverse-proxy.mdx +++ b/content/docs/deploy/reverse-proxy.mdx @@ -14,6 +14,14 @@ For production, run Memos behind a reverse proxy such as Nginx, Caddy, or Traefi In Memos 0.31, `MEMOS_INSTANCE_URL` and access policy are independent. A private instance can have a canonical URL behind a publicly reachable proxy. Select private or public access through the `ACCESS` instance setting; changing the URL does not change an existing access policy. The URL must be absolute HTTP(S), without credentials, a query, or a fragment. +## Client addresses and rate limiting + +Memos rate-limits sign-in, signup, link previews, uploads, and writes by default (`MEMOS_RATE_LIMIT`, 26.10+). Limits for anonymous requests are counted per client address. Memos only reads that address from `X-Forwarded-For` or `X-Real-Ip` when the request comes from a trusted proxy, set with `MEMOS_TRUSTED_PROXIES`: + +- the default, `private`, trusts loopback and private network ranges, which covers a proxy on the same host or Docker network +- if your proxy reaches Memos from a public address, list it, for example `MEMOS_TRUSTED_PROXIES=203.0.113.10` or a CIDR. Otherwise Memos ignores the forwarding headers and every user behind the proxy shares one limit +- if other hosts on your private network are untrusted, or Memos is reached through a NAT hop that shows a private address without rewriting headers, set the real proxy address or `none`. Otherwise a client can forge `X-Forwarded-For` to get a fresh limit + ## Why this matters A reverse proxy gives you: From 9ed2bb8ce912f8ab1f5588037c40c4b61e5a431f Mon Sep 17 00:00:00 2001 From: Swastik Tripathi Date: Tue, 10 Nov 2026 09:13:01 +0530 Subject: [PATCH 2/2] docs: cover proxy chains, header overwriting and Unix sockets; options exist since 0.31 Co-Authored-By: Claude Opus 5.5 --- content/docs/configuration/environment-variables.mdx | 4 ++-- content/docs/deploy/reverse-proxy.mdx | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/content/docs/configuration/environment-variables.mdx b/content/docs/configuration/environment-variables.mdx index 22945877..255f46d8 100644 --- a/content/docs/configuration/environment-variables.mdx +++ b/content/docs/configuration/environment-variables.mdx @@ -22,8 +22,8 @@ All flags and their corresponding environment variables: | `--webhook-private-network-allowlist` | `MEMOS_WEBHOOK_PRIVATE_NETWORK_ALLOWLIST` | empty | Allowed private destinations: exact hostnames, IP addresses, or CIDRs | | `--allow-private-webhooks` | `MEMOS_ALLOW_PRIVATE_WEBHOOKS` | `false` | Deprecated blanket exception for private webhook destinations | | `--log-level` | `MEMOS_LOG_LEVEL` | `info` | Log verbosity (`debug`, `info`, `warn`, `error`) | -| `--rate-limit` | `MEMOS_RATE_LIMIT` | `true` | Request rate limiting for sign-in, signup, link previews, uploads, and writes; `false` turns every limit off (26.10+) | -| `--trusted-proxies` | `MEMOS_TRUSTED_PROXIES` | `private` | Proxies whose forwarding headers identify the client address: IPs, CIDRs, `private` (loopback and private ranges), or `none`; comma-separated (26.10+). See [Reverse Proxy](/docs/deploy/reverse-proxy#client-addresses-and-rate-limiting) | +| `--rate-limit` | `MEMOS_RATE_LIMIT` | `true` | Request rate limiting for sign-in, signup, link previews, uploads, and writes; `false` turns every limit off (0.31+) | +| `--trusted-proxies` | `MEMOS_TRUSTED_PROXIES` | `private` | Proxies whose forwarding headers identify the client address: IPs, CIDRs, `private` (loopback and private ranges), or `none`; comma-separated (0.31+). See [Reverse Proxy](/docs/deploy/reverse-proxy#client-addresses-and-rate-limiting) | | `--config-dir` | `MEMOS_CONFIG_DIR` | `/etc/memos/config` | Directory of [declarative configuration](/docs/configuration/declarative-configuration) files (26.10+) | | `--config-apply` | `MEMOS_CONFIG_APPLY` | `startup` | `startup` applies the configuration directory before serving; `off` leaves it to `memos config apply` (26.10+) | diff --git a/content/docs/deploy/reverse-proxy.mdx b/content/docs/deploy/reverse-proxy.mdx index 54a52047..0590f5b7 100644 --- a/content/docs/deploy/reverse-proxy.mdx +++ b/content/docs/deploy/reverse-proxy.mdx @@ -16,11 +16,13 @@ In Memos 0.31, `MEMOS_INSTANCE_URL` and access policy are independent. A private ## Client addresses and rate limiting -Memos rate-limits sign-in, signup, link previews, uploads, and writes by default (`MEMOS_RATE_LIMIT`, 26.10+). Limits for anonymous requests are counted per client address. Memos only reads that address from `X-Forwarded-For` or `X-Real-Ip` when the request comes from a trusted proxy, set with `MEMOS_TRUSTED_PROXIES`: +Memos rate-limits sign-in, signup, link previews, uploads, and writes by default (`MEMOS_RATE_LIMIT`, 0.31+). Limits for anonymous requests are counted per client address. Memos only reads that address from `X-Forwarded-For` or `X-Real-Ip` when the request comes from a trusted proxy, set with `MEMOS_TRUSTED_PROXIES`: - the default, `private`, trusts loopback and private network ranges, which covers a proxy on the same host or Docker network -- if your proxy reaches Memos from a public address, list it, for example `MEMOS_TRUSTED_PROXIES=203.0.113.10` or a CIDR. Otherwise Memos ignores the forwarding headers and every user behind the proxy shares one limit -- if other hosts on your private network are untrusted, or Memos is reached through a NAT hop that shows a private address without rewriting headers, set the real proxy address or `none`. Otherwise a client can forge `X-Forwarded-For` to get a fresh limit +- list every proxy in the chain that is not on a private address, for example `MEMOS_TRUSTED_PROXIES=private,203.0.113.10` or a CIDR. This includes a CDN or load balancer in front of a local proxy: Memos walks `X-Forwarded-For` from the right and stops at the first untrusted address, so an unlisted public proxy becomes the "client" and every user behind it shares one limit +- if other hosts on your private network are untrusted, or Memos is reached through a NAT hop that shows a private address without rewriting headers, list the real proxy addresses instead of `private`, or set `none`. Otherwise a client can forge `X-Forwarded-For` to get a fresh limit +- configure the outermost proxy to replace any `X-Forwarded-For` and `X-Real-Ip` headers sent by clients instead of appending to them +- when the proxy connects through a Unix socket (`MEMOS_UNIX_SOCK`), there is no peer IP address, so Memos ignores the forwarding headers and all clients share one limit. Connect over TCP if you need per-client limits ## Why this matters