Use case · Document editing

ONLYOFFICE Document Server, self hosted: Docker, JWT, HTTPS and the Nextcloud connector

A self-hosted ONLYOFFICE Document Server is the onlyoffice/documentserver Docker image (ONLYOFFICE Docs Community Edition) running on your own Linux machine, accepting only requests signed (JWT) with a secret you set, behind HTTPS. Your file platform keeps the files; the Document Server opens them in the browser editors and saves them back. Below: sizing, the container and its volumes, the secret, a websocket-aware proxy, the Nextcloud connector, backups, updates and the AGPL terms, from ONLYOFFICE's own documentation.

See the machines

What the Document Server needs

The Document Server opens, converts and co-edits files that the storage platform hands it. ONLYOFFICE publishes two sets of figures, and they differ:

SourceCPURAMSwapDisk
Help centre, Docker install (system requirements)dual core, 2 GHz or better4 GB or moreat least 4 GBat least 40 GB free
Docker-DocumentServer README (recommended)dual-core 2 GHz or higher4 GB or moreat least 2 GBat least 2 GB free

Plan from the help centre column; it is the stricter one, and its OS line asks for an amd64 Linux distribution with kernel 3.10 or later. If Nextcloud or Seafile shares the box, add their requirements on top. For head count, ONLYOFFICE's edition table recommends the Community Edition for up to 20 users; the FAQ below quotes it.

Choosing the machine

A dedicated server gives the editor every core and all the memory, with root and no other tenant, on an unmetered port: no transfer allowance, no overage fee. A racked machine takes about 30 minutes from payment approval to handover; a built-to-order line takes 4 to 24 hours.

  • Document Server alone. The Xeon E3-1585L v5 cartridge line (the m710x): 16 to 64 GB, NVMe, and its own out-of-band console. Other cartridges, apart from the m510, have power control only.
  • Document Server plus Nextcloud or Seafile. A single- or dual-socket rack server on an Intel or AMD (x86) processor: more memory to share, a hardware RAID controller, and an out-of-band console (HPE iLO, Dell iDRAC or a Supermicro BMC) on every racked single-, dual- and quad-socket machine.
  • Not ARM. The help centre asks for amd64, which rules out both ARM lines: the X-Gene cartridge, which boots nothing newer than Ubuntu 18.04, and the 64-core Ampere Altra.

Choose the city nearest your editors from the five locations, ideally the one your storage server is in.

Container, volumes and the JWT secret

First the host: point an A record (and an AAAA record, if you took IPv6 at checkout) for docs.example.com at the server, install Docker Engine and the compose plugin from Docker's own repository, then fence the box with ufw in this order, one line at a time:

  1. sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw so every rule gets an IPv6 copy.
  2. ufw allow OpenSSH (or your own SSH port).
  3. ufw --force enable, which skips the y/n prompt.
  4. ufw allow 80,443/tcp for the host nginx added later. nginx is an ordinary host process, so without this rule it is blocked on IPv4 and IPv6 alike.

The Community image mounts three host directories, per the help centre and README: /var/log/onlyoffice (logs), /var/www/onlyoffice/Data (certificates) and /var/lib/onlyoffice (file cache). The block checks the Engine version, refuses to run if a secret already exists, creates .env at mode 600 before writing to it, and only then starts the container on 127.0.0.1:8080. Run it as root:

v=$(docker version -f '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
if [ "${v:-0}" -lt 28 ]; then
  echo "STOP: Docker Engine 28 or later is needed for a loopback-only port"
elif [ -e /opt/onlyoffice/.env ]; then
  echo "STOP: /opt/onlyoffice/.env exists; not replacing the JWT secret"
else
  mkdir -p -m 700 /opt/onlyoffice && cd /opt/onlyoffice &&
  mkdir -p logs data lib &&
  install -m 600 /dev/null .env &&
  s=$(openssl rand -hex 32) && [ -n "$s" ] && echo "JWT_SECRET=$s" > .env &&
  cat > compose.yaml <<'EOF' &&
services:
  documentserver:
    image: onlyoffice/documentserver:10.0.0.1
    container_name: onlyoffice
    restart: always
    stop_grace_period: 60s
    env_file: /opt/onlyoffice/.env
    environment:
      - JWT_ENABLED=true
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - /opt/onlyoffice/logs:/var/log/onlyoffice
      - /opt/onlyoffice/data:/var/www/onlyoffice/Data
      - /opt/onlyoffice/lib:/var/lib/onlyoffice
EOF
  docker compose -f /opt/onlyoffice/compose.yaml up -d && echo started
fi

Why set the secret by hand: the help centre warns that without JWT_SECRET the generated secret is regenerated on every reboot, breaking integrations. run-document-server.sh writes the secret into local.json with a command that carries it as an argument, so at each container start it is briefly visible in ps to other users of the host. Confirm JWT is on with docker exec onlyoffice documentserver-jwt-status, the tool that script's own start-up message names.

A port mapping with no address is published on 0.0.0.0 and [::]; published traffic skips ufw's rules on IPv4, and on IPv6 only when Docker's own IPv6 networking is enabled. Binding to 127.0.0.1 avoids both, and is local-only on Docker Engine 28 and later (moby#45610), hence the version check. The help centre allows 1-2 minutes for the first start; then curl -s http://127.0.0.1:8080/healthcheck should print true, the same probe the image's Dockerfile HEALTHCHECK uses. Leave the bundled test example off (EXAMPLE_ENABLED defaults to false).

TLS and the websocket proxy

The image can terminate TLS itself, from certificates mounted in /var/www/onlyoffice/Data/certs or through LETS_ENCRYPT_DOMAIN and LETS_ENCRYPT_MAIL. Here a host nginx does it, so the container never listens publicly. DNS first, then the certificate, then the config that names it:

  1. On Ubuntu 24.04: apt install nginx certbot python3-certbot-nginx.
  2. Once docs.example.com resolves to the server: certbot certonly --nginx -d docs.example.com --deploy-hook 'systemctl reload nginx'.
  3. Write the site below, then ln -s /etc/nginx/sites-available/onlyoffice /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx.
# /etc/nginx/sites-available/onlyoffice
map $http_upgrade $proxy_connection {
  default upgrade;
  "" close;
}
server {
  listen 80;
  listen [::]:80;
  server_name docs.example.com;
  return 301 https://$host$request_uri;
}
server {
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name docs.example.com;
  server_tokens off;
  ssl_certificate     /etc/letsencrypt/live/docs.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/docs.example.com/privkey.pem;
  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $proxy_connection;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

It is simplified from proxy-https-to-http.conf in ONLYOFFICE's document-server-proxy repository, which the help centre's proxy article links. Kept: Upgrade and Connection for the editors' websockets, X-Forwarded-Proto plus X-Forwarded-Host (the pair the help centre requires) and X-Forwarded-For. Dropped, among other lines: the upstream block, the map fallbacks for forwarded headers, the cipher and protocol lines (which still allow TLS 1.0 and 1.1), one security header and the commented options.

Wiring Nextcloud and Seafile

Nextcloud uses ONLYOFFICE's connector app. Its docs require the Document Server address to be reachable from users' browsers and from Nextcloud, and the Document Server to be able to POST back to Nextcloud. The storage side is on our Nextcloud page.

  1. Install the ONLYOFFICE app from Nextcloud's app store as an administrator.
  2. In its admin settings, enter https://docs.example.com/ as the Document Server address.
  3. Paste the value from /opt/onlyoffice/.env into the Secret key field. occ and config.php also work, but the form keeps the secret off a command line.
  4. Test the link: occ onlyoffice:documentserver --check.

The connector docs describe the save cycle: the Document Server downloads the file, and when every editor has closed it, after 10 seconds of inactivity, Nextcloud fetches the new version. Nextcloud refuses a self-signed Document Server certificate unless verification is switched off, a setting the docs call insecure. On a shared machine they ask for a custom port or a proxy, which the hostname setup provides.

Seafile: ONLYOFFICE lists it among the sync-and-share platforms its editors work with; its help centre has a Desktop Editors article on connecting to Seafile, but no server-side Seafile connector guide; the settings are in Seafile's own manual, and our Seafile page covers that server. For storage on a private address, note ALLOW_PRIVATE_IP_ADDRESS (default false), which decides whether the Document Server may connect to private IP addresses.

Backups, restore and updates

Documents live in Nextcloud or Seafile and belong in that platform's backup. This box holds the JWT secret, compose file, Data directory, file cache, nginx site and certificates, and the nightly job archives all of them. It first runs documentserver-prepare4shutdown, which the README prescribes before a stop to avoid data loss (up to 5 minutes), then stops, archives, starts and tests the archive. Expect a short editing outage at 03:30.

install -d -m 700 /var/backups/onlyoffice /var/log/onlyoffice-backup
install -m 700 /dev/null /usr/local/sbin/onlyoffice-backup.sh
cat > /usr/local/sbin/onlyoffice-backup.sh <<'EOF'
set -o pipefail
umask 077
c=/opt/onlyoffice/compose.yaml
f=/var/backups/onlyoffice/onlyoffice-$(date +%F).tar.gz
docker exec onlyoffice documentserver-prepare4shutdown
docker compose -f "$c" stop
tar --numeric-owner -czf "$f" -C / opt/onlyoffice \
  etc/nginx/sites-available/onlyoffice etc/letsencrypt
rc=$?
docker compose -f "$c" start
[ "$rc" -eq 0 ] && tar -tzf "$f" > /dev/null && echo "ok $f" ||
  { echo "FAILED $f"; exit 1; }
find /var/backups/onlyoffice -name 'onlyoffice-*.tar.gz' -mtime +14 -delete
EOF
printf '%s\n' '30 3 * * * root bash /usr/local/sbin/onlyoffice-backup.sh > /var/log/onlyoffice-backup/last.log 2>&1' > /etc/cron.d/onlyoffice-backup

Copy the archives off the machine yourself. For a push from root's cron, create root's key first (ssh-keygen -t ed25519, then ssh-copy-id backup@203.0.113.10), append rsync -a /var/backups/onlyoffice/ backup@203.0.113.10:onlyoffice/ to the script, and prune on the receiving host. No --delete: an emptied source would empty the copies.

Live restore of DATE's archive, as root (sudo -i first); stop at any step that does not print its ok:

  1. tar -tzf /var/backups/onlyoffice/onlyoffice-DATE.tar.gz > /dev/null && echo archive ok
  2. docker exec onlyoffice documentserver-prepare4shutdown; docker compose -f /opt/onlyoffice/compose.yaml down && echo stopped ok
  3. tar --numeric-owner -czf /root/onlyoffice-before-restore.tar.gz -C / opt/onlyoffice && tar -tzf /root/onlyoffice-before-restore.tar.gz > /dev/null && [ ! -e /opt/onlyoffice.old ] && mv /opt/onlyoffice /opt/onlyoffice.old && echo set-aside ok
  4. tar --numeric-owner -xzpf /var/backups/onlyoffice/onlyoffice-DATE.tar.gz -C / opt/onlyoffice && echo restored ok (add the nginx and letsencrypt paths on a new machine).
  5. docker compose -f /opt/onlyoffice/compose.yaml up -d, then the curl check and occ onlyoffice:documentserver --check.
  6. Only when both pass: rm -rf /opt/onlyoffice.old.

Updates: ONLYOFFICE supports only the latest stable version; its update guide says back up, disconnect users with prepare4shutdown, then run the new image with the same mounts. Here: run the backup script, change the tag with sed -i 's|documentserver:10.0.0.1|documentserver:NEW_TAG|' /opt/onlyoffice/compose.yaml, then docker compose -f /opt/onlyoffice/compose.yaml pull. The backup restarted the container and editors may have reconnected, so run docker exec onlyoffice documentserver-prepare4shutdown again right before docker compose -f /opt/onlyoffice/compose.yaml up -d. To roll back, put the old image tag back and restore the archive the backup script made just before the update, following the restore steps.

Licence: AGPL-3.0 and ONLYOFFICE's additional terms

The LICENSE file in the Docker-DocumentServer repository is the GNU AGPL v3, supplemented under section 7 by ONLYOFFICE's additional terms: keep every copyright, licence and attribution notice; mark modified versions as modified, with dates, and as based on ONLYOFFICE; show the legal notices in any interactive interface; non-code content such as icon sets is CC BY-SA 4.0. Term 4 grants no rights to ONLYOFFICE's trademarks, names or logos. The Community licensing FAQ adds that the Community version must keep its original branding.

Our general reading, not legal advice: running the unmodified image for your own organisation adds no duty. Distributing copies, modified or not, obliges you to pass on the corresponding source under the AGPL. And because it is the AGPL, a modified build that users reach over a network obliges you to offer those users its source as well.

Questions

Is ONLYOFFICE Docs Community Edition capped at 20 users?

ONLYOFFICE's edition table gives the Community Edition's number of users as up to 20 recommended: a recommendation, in its words. The Community licensing FAQ says the edition is intended for testing, personal use, development and small-scale deployments, and recommends a commercial licence for production environments and commercial distribution.

Where are the documents stored?

In the platform that calls the editor, such as Nextcloud. The Document Server downloads a file when it is opened and, once every editor has closed it, the storage fetches the saved version. Its own volumes hold logs, a file cache and certificates.

What breaks if JWT_SECRET is left unset?

JWT stays on, since it is enabled by default from version 7.2, but the help centre says the generated secret is regenerated on every reboot. Nextcloud then holds a stale secret and its requests fail until you paste the new one. A fixed value in .env avoids that.

Does the Community image need a database volume?

The current help centre install guide and README mount three volumes for Community: logs, data and lib. The README lists PostgreSQL, RabbitMQ and Redis volumes for the Enterprise and Developer images, which bundle those services. The update guide's backup example still names a db folder, so back one up too if an older install of yours mounts it.

Run the editors on your own metal

Pick a rack server or a cartridge line, check its memory and city, and configure it.

See the machines

Related on Server Room

Other use cases