How to generate and configure the JWT secret for ONLYOFFICE DocumentServer

ONLYOFFICE DocumentServer adds a JSON Web Token to every API call between the
editors (browser) and the backend services, signed by a shared secret. The
token prevents an unauthenticated browser from directly talking to your
editors and forcing them to render arbitrary documents. Since v7.2 JWT
is enabled by default and the secret is generated automatically on first
boot
— meaning out-of-the-box you don’t need to do anything. The two cases
where you must touch this are: integrating DocumentServer with an external
service (Nextcloud, WordPress, Confluence, your own app), or running it
behind a load balancer that needs to know the secret ahead of time.

This is the full walk-through for a 2026 install on Linux + Docker: how to
read the current secret, change it to your own, and avoid the two traps
that account for 90% of “DocumentServer rejected my JWT” threads.

Where the secret lives

For a native Linux install (.deb / .rpm):

Bash
/etc/onlyoffice/documentserver/local.json

For a Docker install: there is no file by default. The secret is generated
on first boot and held in container memory + the named volume you mount
.
You set your own via the JWT_SECRET environment variable when the
container is started.

In both cases the secret lives in the same JSON path:

Json
{
  "services": {
    "CoAuthoring": {
      "token": {
        "enable": {
          "browser": true,
          "request": {
            "inbox": true,
            "outbox": true
          }
        }
      },
      "secret": {
        "inbox":   { "string": "YOUR_SECRET_HERE" },
        "outbox":  { "string": "YOUR_SECRET_HERE" },
        "session": { "string": "YOUR_SECRET_HERE" }
      }
    }
  }
}

Three things to notice:

  • The secret must be identical in inbox, outbox, and session.
    This is the single most common reason JWT auth fails — an admin copies
    the secret file from one server, pastes into another, and forgets the
    third section.
  • Token verification is on by default since 7.2. Pre-7.2 you also have
    to flip all three enable.* flags from false to true. If you
    upgrade from an old version, manually configured secrets are preserved
    but the enable flags stay where you left them.
  • The HTTP header carrying the token is Authorization: Bearer ***
    (default), with the JWT being standard HS256.

Changing the secret — Linux native

Bash
sudo nano /etc/onlyoffice/documentserver/local.json
# replace YOUR_SECRET_HERE in three places — must be the same string

sudo systemctl restart ds-converter ds-docservice ds-metrics
# Confirm with:
sudo cat /etc/onlyoffice/documentserver/local.json | grep '"string"'
# You should see YOUR_SECRET_HERE three times in a row.

The restart order matters: ds-converter (handles file conversion to
the browser-friendly formats), then ds-docservice (the actual editor
service), then ds-metrics (Prometheus exporter). Restart them in that
order so old token sessions die cleanly before the new secret gets
loaded.

Changing the secret — Docker

For Docker, never edit local.json inside the container. The
container’s filesystem is ephemeral and any manual change gets nuked on
restart. Use environment variables:

Bash
docker run -i -t -d -p 80:80 
  -e JWT_ENABLED=true 
  -e JWT_SECRET='YOUR_SECRET_HERE' 
  -e JWT_HEADER=Authorization 
  -e JWT_IN_BODY=false 
  onlyoffice/documentserver

Notes:

  • JWT_ENABLED=false disables validation entirely. Don’t use this in
    production — it’s debug-only.
  • JWT_HEADER=AuthorizationJwt (the older convention) is still supported;
    the default is Authorization since v6.x.
  • JWT_IN_BODY=false means the token is carried in the
    Authorization header. If true, ONLYOFFICE expects the token in the
    request body. Default is false.
  • JWT_SECRET is read once at startup. You must recreate the container
    to change the secret.
    docker restart won’t pick it up.

After changing JWT_SECRET, also update the matching secret in the
connecting service
(Nextcloud’s ONLYOFFICE connector, your app’s
config, whatever).

Generating a good secret

The default generation uses a 64-char random hex string. If you set one
yourself, use something equivalently un-guessable. Don’t use a UUID,
don’t use your team’s slack channel name. openssl rand -hex 32 is more
than enough:

Bash
openssl rand -hex 32
# → 7c2c89ff04e3... (64-char hex string)

The secret just has to be identical on both sides (DocumentServer +
connector); no other characters have meaning. Picking a 64-char hex avoids
all “your string contains a $ and bash ate it” edge cases.

Reading your current secret

If you forgot what you set it to:

Bash
# Linux native
cat /etc/onlyoffice/documentserver/local.json | 
  jq -r '.services.CoAuthoring.secret | to_entries[] | .value.string'

# Docker (without restarting the container)
docker exec documentserver 
  cat /etc/onlyoffice/documentserver/local.json | 
  jq -r '.services.CoAuthoring.secret | to_entries[] | .value.string'

If jq isn’t installed, a grep -A 1 'string' loop over the file works
the same way.

The two traps

1. Secret mismatch after upgrade

A lot of “DocumentServer keeps rejecting my JWT” GitHub issues come from
this exact scenario: the connector was configured for the old secret,
and either the user (or an automatic upgrade script) generated a new
random one. The DocumentServer now sits with two equal-but-different
secrets across versions. The fix is the same string in both places,
exactly — including no leading/trailing whitespace.

2. JWT_IN_BODY=false vs true defaults changed across versions

In v6.x, the default for JWT_IN_BODY was false. In some v7.x point
releases, depending on how you migrated, it can end up true. If your
connector works at one moment and breaks the next, check this flag.

Bash
docker exec documentserver cat /etc/onlyoffice/documentserver/local.json 
  | jq '.services.CoAuthoring.token.enable | .browser, (.request.inbox, .request.outbox)'

You should see true for all three. If any are false, you overrode them
during the migration.

What I actually run in 2026

For a Docker deploy on a single VM, with the connector config held in a
file under version control:

Yaml
# docker-compose.yaml
services:
  documentserver:
    image: onlyoffice/documentserver
    restart: unless-stopped
    environment:
      JWT_ENABLED: "true"
      JWT_SECRET_FILE: /run/secrets/jwt_secret
      JWT_HEADER: Authorization
    volumes:
      - /var/run/secrets/jwt_secret:/run/secrets/jwt_secret:ro
      - documentserver_data:/var/lib/onlyoffice
    ports:
      - "8080:80"

Generating the secret via Docker secrets (openssl rand -hex 32 | docker secret create jwt_secret -) means I can rotate it by re-seeding
the secret without touching docker-compose.yaml itself. The
JWT_SECRET_FILE env var reads the secret from a file path —
available since v7.4 — and is the right pattern when you’re running
under Swarm or any orchestrator with a secrets store.

Sources

Last modified: 2026年7月16日

Author

Comments

Write a Reply or Comment

Your email address will not be published.