schedule Published: September 20, 2025
update Last updated: December 14, 2025

Production-ready setup for FileBrowser Quantum and OnlyOffice behind Traefik reverse proxy with automatic HTTPS certificates from Let’s Encrypt.

Overview

In this guide we’ll use Docker compose to setup FileBrowser Quantum and OnlyOffice behind Traefik Reverse Proxy for a secure production-ready deployment. You can use this guide as reference. If you want to copy-paste you also can, but remember to change all the fake info here with your own.

That said, this guide will cover:

  • Automatic HTTPS and certificate renewal with Let’s Encrypt certificates, this is managed automatically by Traefik (every 60 days aprox).
  • Secure JWT authentication between services (FileBrowser and OnlyOffice).
  • Docker compose with Traefik labels configuration for the services.
  • A Production-ready setup with proper security headers.

Pre-requisites

  • Docker and Docker Compose installed, see how to install docker for your respective OS.
  • A valid and working Domain name with a DDNS provider configured which is required for Let’s Encrypt DNS Challenge in traefik.
  • DDNS provider (Dynu, Cloudflare, DuckDNS, etc) - See supported providers
  • Basic understanding of Traefik, Docker, and Docker compose in general.
  • Email address for Let’s encrypt.

Directory Structure

A quick overview of how will look your directory structure following the steps in the guide:

SHELL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
services/
├── filebrowser/
│   ├── data/
│   │   ├── config.yaml        # Your filebrowser configuration
│   │   └── database.db        # Database of filebrowser
│   ├── docker-compose.yaml    # Filebrowser compose file
│   └── .env                   # Environment file for filebrowser
├── onlyoffice/
│   ├── docker-compose.yaml    # Onlyoffice compose file
│   └── .env                   # Environment file for onlyofffice
└── traefik/
    ├── certs/
    │   └── acme.json          # Should have chmod 600 permissions, all the certificates will be stored on this file.
    ├── config/
    │   ├── dynamic/
    │   │   └── middlewares.yaml  # Dynamic config (File provider) of traefik, used for the security headers.
    │   └── static/
    │       └── traefik.yaml   # Static config of traefik
    ├── docker-compose.yaml    # Traefik compose file
    └── .env                   # Environment file for Traefik

Step 1: Create Docker Network

All the three services must be on the same network:

SHELL
1
2
3
4
5
6
7
docker network create \ 
  --driver=bridge \ 
  --subnet=192.168.2.0/24 \ 
  --gateway=192.168.2.1 \ 
  --ip-range=192.168.2.128/25 \ 
  --label=Reserved_IPs="192.168.2.2-127" \ 
  proxy_network 

Step 2: Setup Traefik

Create Traefik Directory

SHELL
1
2
mkdir services && cd services
mkdir -p traefik/certs traefik/config/dynamic traefik/config/static

Traefik Docker Compose

Create traefik/docker-compose.yaml:

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    security_opt:
      - no-new-privileges=true
    ports:
      - "80:80"
      - "443:443"
    environment:
      - DYNU_API_KEY=${DYNU_API_KEY} # we are using dynu - check the traefik documentation about other providers
      - TRAEFIK_DASHBOARD_CREDENTIALS=${TRAEFIK_DASHBOARD_CREDENTIALS}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config/static/traefik.yaml:/etc/traefik/traefik.yaml:ro
      - ./certs/acme.json:/var/traefik/certs/acme.json:rw
      - ./config/dynamic:/etc/traefik/config:ro
    networks:
      - proxy_network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik.tls.options=default"
      - "traefik.http.routers.traefik.rule=Host(`traefik.${DOMAIN_NAME}`)"
      - "traefik.http.routers.traefik.tls.domains[0].main=${DOMAIN_NAME}"
      - "traefik.http.routers.traefik.tls.domains[0].sans=*.${DOMAIN_NAME}"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.services.traefik.loadbalancer.server.port=8080"
      - "traefik.http.services.traefik.loadbalancer.passhostheader=true"
      - "traefik.http.routers.traefik.middlewares=security-headers@file"

      # Basic auth on dashboard
      - "traefik.http.routers.traefik.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}"

    restart: unless-stopped

networks:
  proxy_network:
    external: true

Traefik Environment File

Create traefik/.env:

BASH
1
2
3
4
5
DOMAIN_NAME=your-domain.com         # Your root domain here.
DYNU_API_KEY=your-dynu-api-key-here # DDNS API Key (we are using dynu - check the traefik documentation about other providers)

# Generate with: docker run --rm httpd:alpine htpasswd -nb your-username your-password | sed -e 's/\$/\$\$/g'
TRAEFIK_DASHBOARD_CREDENTIALS=your-username:$$apr1$$5CK4UFEP$$gE8rmbSMI0Lfb9vPE9I2j/

Generate dashboard credentials:

Replace your-username and your-password with your own user and password, then paste the output in the .env file we created.

BASH
1
docker run --rm httpd:alpine htpasswd -nb your-username your-password | sed -e 's/\$/\$\$/g'

Create Certificate File

BASH
1
2
touch traefik/certs/acme.json
chmod 600 traefik/certs/acme.json

Traefik Configuration

Create traefik/config/static/traefik.yaml:

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
global:
  checkNewVersion: true      # Check if new version of traefik is available
  sendAnonymousUsage: false

api:
  dashboard: true
  insecure: false

# Log level. You can use DEBUG initially for see if the certificates are being pulled correctly
log:
  level: INFO # DEBUG, INFO, WARN, ERROR, FATAL, PANIC
  
entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true

  websecure:
    address: :443
    http:
      tls:
        certResolver: letsencrypt
    transport:
      respondingTimeouts:
        readTimeout: "300s"
        writeTimeout: "300s"
        idleTimeout: "360s"
    http2:
      maxConcurrentStreams: 500
    asDefault: true

serversTransport:
  insecureSkipVerify: false
  forwardingTimeouts:
    dialTimeout: "60s"           # Time to establish TCP connection
    responseHeaderTimeout: "60s" # Time for backend to send headers
    idleConnTimeout: "90s"       

http:
  timeouts:
    readTimeout: "300s"
    writeTimeout: "300s"
    idleTimeout: "360s"

tls:
  options:
    modern:
      minVersion: 'VersionTLS13'
    default:
      minVersion: 'VersionTLS12'
      cipherSuites:
        - 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256'
        - 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'
        - 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384'
        - 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'
        - 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305'
        - 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305'

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    network: "proxy_network"
    exposedByDefault: false    
  file:
    directory: "/etc/traefik/config" # Directory mounted in the compose file, here we will define our security headers for the services - Also called File Provider
    watch: true

certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@provider.com  # Replace with your email
      storage: /var/traefik/certs/acme.json
      # Use staging first for testing first:
      # caServer: https://acme-staging-v02.api.letsencrypt.org/directory
      # Then switch to production:
      caServer: https://acme-v02.api.letsencrypt.org/directory # certificates for production
      dnsChallenge:
        provider: dynu
        resolvers:
        - "1.1.1.1:53"
        - "1.0.0.1:53"
        # Uncomment if having issues when pulling the certificates.
        # disablePropagationCheck: true
        # delayBeforeCheck: "60s"

Define the security middleware headers:

They will go in our dynamic config directory of traefik (Also called File Provider).

Create traefik/config/dynamic/middlewares.yaml:

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
http:
#  Each middleware defined need to be referenced in each service docker-compose via labels to use them.
  middlewares:
    # Global security headers
    security-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        customFrameOptionsValue: "SAMEORIGIN"
        referrerPolicy: "strict-origin-when-cross-origin"
        customRequestHeaders:
          X-Forwarded-Proto: "https"
          X-Real-IP: "true"
          X-Forwarded-For: "true"

    # Same as security headers, but letting the site being embedded (allowing i-frames) which onlyoffice needs.
    no-frame-block:
      headers:
        sslRedirect: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        referrerPolicy: "strict-origin-when-cross-origin"
        customRequestHeaders:
          X-Forwarded-Proto: "https"
          X-Real-IP: "true"
          X-Forwarded-For: "true"

    # This middleware is to disable buffering, only use it if you have issues with SSE or uploading large files to filebrowser
    limit:
      buffering:
        maxRequestBodyBytes: 0
        maxResponseBodyBytes: 0
        memRequestBodyBytes: 0
        memResponseBodyBytes: 0


    # This middleware is more for internal services, like APIs or databases.
    security-minimal:
      headers:
        sslRedirect: true
        contentTypeNosniff: true
        stsPreload: true
        customRequestHeaders:
          X-Forwarded-Proto: "https"

Deploy Traefik

Just like any other service using docker compose. You should be on the directory where you docker-compose.yaml is located and:

SHELL
1
docker compose up -d

See the logs of traefik to see what’s happening (CTRL+C to exit):

SHELL
1
docker logs traefik -f

Test that traefik is working

For test if traefik is working you can visit the dashboard of traefik at: https://traefik.your-domain.com (the domain that you used on the .env file), and log in with the credentials that you generated before.

traefik-dashboard

If the page is working you did all correctly! Let’s go with Filebrowser and OnlyOffice now.

Step 3: Setup OnlyOffice

Create OnlyOffice Directory

BASH
1
2
cd services # the same directory where traefik is
mkdir -p onlyoffice

OnlyOffice Docker Compose

Create onlyoffice/docker-compose.yaml:

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
services:
  onlyoffice:
    container_name: onlyoffice
    image: onlyoffice/documentserver
    environment:
      TZ: $TZ
      JWT_SECRET: $ONLYOFFICE_SECRET
      ONLYOFFICE_HTTPS_HSTS_ENABLED: false
    #ports:
     # - '8081:80'  # Commented out because traefik will handle it.
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.tls=true"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.entrypoints=websecure"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.tls.certresolver=letsencrypt"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.tls.options=default"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.rule=Host(${DOMAIN_NAME})"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.service=${TRAEFIK_SERVICE_NAME}"
      - "traefik.http.services.${TRAEFIK_SERVICE_NAME}.loadbalancer.server.port=${TRAEFIK_SERVICE_PORT}"
      - "traefik.http.services.${TRAEFIK_SERVICE_NAME}.loadbalancer.passhostheader=true"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.middlewares=no-frame-block@file"   # no-frame-block header that we defined in the middlewares.yaml config file.

      # Headers for onlyoffice, see https://github.com/ONLYOFFICE/onlyoffice-nextcloud/issues/151 for details
      - "traefik.http.middlewares.${TRAEFIK_SERVICE_NAME}-headers.headers.accesscontrolalloworiginlist=*"
    networks:
      proxy_network:
    restart: unless-stopped

networks:
  proxy_network:
    external: true

Generate JWT Secret

BASH
1
2
openssl rand -base64 32
# Example output: KDSpcNwKAos2moijgdfi9jf9wr3wek=

OnlyOffice Environment File

Create onlyoffice/.env:

BASH
1
2
3
4
5
TRAEFIK_SERVICE_NAME=onlyoffice
TRAEFIK_SERVICE_PORT=80
DOMAIN_NAME=`office.yourdomain.com`   # Don't forget the backticks, they are critical for traefik
ONLYOFFICE_SECRET=KDSpcNwKAos2moijgdfi9jf9wr3wek=  # Your output of the openssl command
TZ=America/New_York  # Your timezone

Deploy OnlyOffice

SHELL
1
docker compose up -d

Check the logs to see what’s happening (CTRL+C to exit):

SHELL
1
docker logs onlyoffice -f

Test that OnlyOffice is working

  • Visit your onlyoffice URL on your browser (https://office.yourdomain.com) , you should see something like this:
office
  • You can also verify with curl:
SHELL
1
2
curl https://office.yourdomain.com/healthcheck
# Should return "true", that means that the healthcheck is active and working

Step 4: Setup FileBrowser Quantum

Now let’s go with Filebrowser!

Create Filebrowser Directory

BASH
1
2
3
cd services  # the same directory where traefik and onlyoffice are.
mkdir -p filebrowser/data
touch filebrowser/data/database.db

FileBrowser Docker Compose

Create filebrowser/docker-compose.yaml:

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
services:
  filebrowser:
    container_name: filebrowser
    image: gtstef/filebrowser:latest
    env_file: .env
    environment:
      FILEBROWSER_CONFIG: "data/config.yaml"
      FILEBROWSER_DATABASE: "data/database.db"
      FILEBROWSER_ADMIN_PASSWORD: ${FILEBROWSER_ADMIN_PASSWORD}
      TZ: ${TZ}
    volumes:
      - './data:/home/filebrowser/data'
      - '/files:/files'
      - '/other/path/media:/media'
    user: filebrowser
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.tls=true"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.entrypoints=websecure"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.tls.certresolver=letsencrypt"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.tls.options=default"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.rule=Host(${DOMAIN_NAME})"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.service=${TRAEFIK_SERVICE_NAME}"
      - "traefik.http.services.${TRAEFIK_SERVICE_NAME}.loadbalancer.server.port=${TRAEFIK_SERVICE_PORT}"
      - "traefik.http.services.${TRAEFIK_SERVICE_NAME}.loadbalancer.passhostheader=true"
      - "traefik.http.routers.${TRAEFIK_SERVICE_NAME}.middlewares=security-headers@file" # Security headers, you can also add the 'limit' middleware if you have issues with large file uploads.  'security-headers@file,limit@file'

    networks:
      proxy_network:
    restart: unless-stopped

networks:
  proxy_network:
    external: true

FileBrowser Environment File

Create filebrowser/.env:

BASH
1
2
3
4
5
FILEBROWSER_ADMIN_PASSWORD=YourSecurePassword
DOMAIN_NAME=files.yourdomain.com
TRAEFIK_SERVICE_NAME=filebrowser
TRAEFIK_SERVICE_PORT=80
TZ=America/New_York # Your Timezone

FileBrowser Configuration

See configuration overview if you want better explanations about the config file.

Create filebrowser/data/config.yaml:

YAML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
server:
  port: 80
  baseURL: "/"
  sources:
  # Sources for filebrowser, the path should match your volume mount (of the right)
    - path: "/files"
      name: "Files"
      config:
        defaultEnabled: true
        createUserDir: true
    - path: "/media"
      name: "Media"
      config:
        defaultEnabled: false
        createUserDir: false

  # Critical for OnlyOffice integration
  externalUrl: "https://files.yourdomain.com"
  internalUrl: "http://filebrowser:80"

auth:
  tokenExpirationHours: 6
  adminUsername: admin   # Your admin username, change this.
  methods:
    password:
      enabled: true
      minLength: 5 # Minimum lenght of the password
      signup: false

userDefaults:
  darkMode: true
  locale: "en"
  disableOnlyOfficeExt: ".txt .html .md"

integrations:
  office:
    url: "https://office.yourdomain.com"       # The URL to the OnlyOffice Server that you defined in the onlyoffice .env
    internalUrl: "http://onlyoffice:80"        # Optional internal URL, uses the container name to communicate.
    secret: "KDSpcNwKAos2moijgdfi9jf9wr3wek="  # Should be the SAME as OnlyOffice secret (also in onlyoffice .env)
    viewOnly: false

Deploy FileBrowser

SHELL
1
docker compose up -d

Check the logs to see what is happening (CTRL+C to exit):

SHELL
1
docker logs filebrowser -f

Test that FileBrowser is working

  • Open your filebrowser domain on your browser (https://files.yourdomain.com), you should see the login page.
  • Login with your credentials that you configured in both: The password in the .env file, and the username in config.yaml.

Testing the Integration

  1. Navigate to your filebrowser domain (https://files.yourdomain.com), and login.
  2. Upload a test .docx (or any document) file.
  3. Open the file - The file should open in OnlyOffice.
  4. If all loaded fine, try to make edits and then save.
  5. Verify changes persisted (close and open the file again)

Enable Debug Mode

If you have issues with onlyoffice, you should enable the office debug mode in filebrowser. To do that go to settings -> profile settings -> File Viewer Options in UI and enable the debug mode.

office-debug-mode

After that open any document and check the debug window, also check the logs of both containers as well. See troubleshooting for more detailed solutions.

Certificate Management

Check Certificate Status

You will need to have the jq package installed if you want a colored output.

BASH
1
2
3
4
5
# View certificate details
docker exec traefik cat /var/traefik/certs/acme.json | jq

# Check certificate expiration
openssl s_client -connect files.yourdomain.com:443 -servername files.yourdomain.com < /dev/null 2>/dev/null | openssl x509 -noout -dates

Certificate Renewal

Traefik automatically renews certificates 30 days before expiration. No manual action needed!

Force Renewal (if needed):

Navigate to your traefik directory.

BASH
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Stop Traefik
docker compose down -v

# Backup and clear certificates
cp certs/acme.json certs/acme.json.bak
rm -rf certs/acme.json
touch certs/acme.json
chmod 600 certs/acme.json

# Restart Traefik (will request new certificates)
docker compose up -d --force-recreate

Maintenance

Update Services

BASH
1
2
docker compose pull                     # Pull the latest image available of the service.
docker compose up -d --force-recreate   # Re-create the container and restart it automatically.

View Logs

BASH
1
docker logs filebrowser -f # or onlyoffice, traefik (by service name)

Troubleshooting

Certificates Not Generating

If you notice that you certificates are not being generated, the first thing that you should do is check your traefik logs.

First enable DEBUG logs in the traefik.yaml config file:

YAML
1
2
log:
  level: DEBUG # DEBUG, INFO, WARN, ERROR, FATAL, PANIC

Then restart the service and check the logs:

BASH
1
2
docker restart traefik
docker logs traefik -f | grep acme
  • Also try uncommenting the last lines on the traefik.yaml config file.
  • And check the acme.json file. You should see the certificates there, for that, first navigate to the directory where your services are, then do this command:
SHELL
1
2
# jq is optional, is just for colorize the output.
cat traefik/certs/acme.json | jq

Common issues:

  • Wrong DDNS API key, make sure that you copied it correctly from your DDNS provider.
  • TXT records could not be propagated yet, you will need to wait a bit if is the first pulling the certificates, can take around 5 minutes.
  • Maybe you were rate-limited due to multiple certs generation, you should use staging first for test, that way you’re not rate-limited.
  • Wrong acme.json permissions.
  • Wrong domain.

OnlyOffice Shows Blank Screen

  1. Check if OnlyOffice is healthy/running:
BASH
1
curl https://office.yourdomain.com/healthcheck

Or visit your onlyoffice domain on the browser, the page should load.

  1. Verify JWT secrets match. Should be same on onlyoffice and filebrowser.
  2. Check browser console for CORS errors.
  3. Enable office debug mode in FileBrowser.

Also ensure that all the external URLs use https://:

YAML
1
2
3
4
5
6
7
8
server:
  externalUrl: "https://files.yourdomain.com"
  internalUrl: "http://filebrowser:80"

integrations:
  office:
    url: "https://office.yourdomain.com"
    internalUrl: "http://onlyoffice:80"

Other issues

If you have other issues related with onlyoffice and filebrowser, you should check the Office Troubleshooting guide, which covers some of the most commons issues with solutions.

External Resources

Next Steps

Credits

This guide is a community-contributed guide by @Kurami32. Thank you for sharing your working setup!