Paperless-NGX Setup: Installation, Security, and NGINX Integration

When I read about paperless-ngx, I was immediately drawn to the idea of having all my documents indexed (via OCR) and centrally stored. With a proper tagging system, exporting my documents for my annual tax declaration should only take seconds.

The installation procedure is straightforward but contains several critical security pitfalls that must be addressed, especially when integrating a reverse proxy. Here are my notes on setting up Paperless-NGX in Debian 12 Bookworm.

Part I: Installation and Secure User Setup

1. Install Docker Engine

Please consult the official Docker documentation for the installation of the Docker Engine.

2. Add a Dedicated, Unprivileged User

The safest approach is to use a dedicated system user. This ensures the application does not run with root privileges, even if the installation script or containers were ever compromised.

# 1. Create dedicated system user 'paperless'
adduser paperless --system --home /opt/paperless --group

# 2. Grant the user permissions to use Docker
usermod -aG docker paperless

3. Run the Install Script Securely

Execute the official install script using the newly created, unprivileged paperless user by leveraging sudo -Hu paperless.

sudo -Hu paperless bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"

My Configuration Settings during the script:

SettingRecommended ValueRationale
URLhttps://documents.example.comNecessary for reverse-proxy and SSL configuration.
Database backendpostgresRecommended for production and better performance compared to SQLite.
Enable Apache Tika?yesRequired for indexing complex document types (Word, Excel, PowerPoint).
OCR languagedeu+eng+fra+araCaution: Each language increases resource usage. Choose only what is necessary.

Part II: Configuration and Container Management (Beginner Guide)

1. Modifying Configuration (docker-compose.env)

The environment variables are managed via the docker-compose.env file located in the installation directory (/opt/paperless/paperless-ngx/).

I recommend immediately setting the following variables, which are essential for security and functionality:

PAPERLESS_URL=https://documents.example.com
PAPERLESS_SECRET_KEY=------------USE-A-LONG-CRYPTIC-RANDOM-KEY----------------
PAPERLESS_OCR_LANGUAGE=ara+deu+eng+fra
PAPERLESS_OCR_LANGUAGES=ara deu eng fra # Note: space vs. plus sign syntax
PAPERLESS_CONSUMER_RECURSIVE=true
PAPERLESS_PORT=8000
  • OCR Note: Be sure to set both variables (_LANGUAGE and _LANGUAGES) as the syntax requirements for the Tesseract engine and the Docker Compose files differ.
  • CONSUMER_RECURSIVE: Set to true to allow dropping folders into the consume directory.

2. Container Management: Start, Stop, and Update

For users new to Docker, knowing the exact commands for managing the environment after configuration changes is essential.

First, navigate to the directory containing the configuration files:

# cd /opt/paperless/paperless-ngx/

Stop and Restart (After configuration change):

root@paperless:/opt/paperless/paperless-ngx# sudo -Hu paperless docker compose down
[+] Running 6/6
 ✔ Container paperless-webserver-1  Removed                                                   6.9s 
...
root@paperless:/opt/paperless/paperless-ngx# sudo -Hu paperless docker compose up -d
[+] Running 6/6
 ✔ Network paperless_default        Created                                                   0.1s 
...
 ✔ Container paperless-webserver-1  Started                                                   0.0s

Update (Pulling new container images):

root@paperless:/opt/paperless/paperless-ngx# sudo -Hu paperless docker compose down
root@paperless:/opt/paperless/paperless-ngx# sudo -Hu paperless docker compose pull
[+] Pulling 35/22
...

Part III: Critical Security Fix and NGINX Integration

1. CRITICAL SECURITY FLAW: Port Exposure Fix

The default installation (as of writing this article: 17. Dezember 2023) does not bind the Paperless-NGX webserver (Port 8000) to localhost (127.0.0.1). This means if you lack a strict host firewall, the Paperless login page is accessible from the internet via Port 8000.

Proof of Exposure: A netstat check shows global listening:

tcp        0      0 0.0.0.0:8000            0.0.0.0:* LISTEN

The Fix: You must edit the ports directive in the docker-compose.yml to explicitly set the binding to 127.0.0.1.

# /opt/paperless/paperless-ngx/docker-compose.yml (webserver section)
    ports:
      # CRITICAL: Only the localhost can reach Port 8000 on the host.
      - "127.0.0.1:8000:8000" 

2. NGINX SSL/TLS Basic Hardening

Since Paperless-NGX handles sensitive personal documents, a strong TLS configuration is mandatory. I suggest using the Mozilla SSL Configuration Generator as a reference for modern best practices.

Recommendations:

  • ECDSA Certificates: Use ECDSA certificates (e.g., secp384r1) over legacy RSA keys for better performance and security.
  • HSTS: Implement Strict-Transport-Security (HSTS) to force browsers to always use HTTPS.
  • TLS Protocol: Use ssl_protocols TLSv1.3; to ensure only the most current and secure protocol is allowed.

3. Header Management and Inheritance Logic

A common pitfall with NGINX is the add_header directive. If you use even one add_header directive within a location {} block, it overrides/disables all header inheritance from the parent server {} block.

This means if you add the Referrer-Policy header in your location / {} block, you must re-declare all other global headers (like HSTS and other security headers) there as well.

4. Essential Security Headers

To ensure defense against common web attacks, I use a separate headers.conf file:

Nginx

# headers.conf in /etc/nginx/conf.d/
add_header X-Frame-Options "SAMEORIGIN" always;         # Clickjacking Defense
add_header X-Content-Type-Options "nosniff" always;    # MIME-Sniffing Defense
add_header X-XSS-Protection "0" always;                # Disables obsolete browser protection
add_header Permissions-Policy "camera=(), microphone=()" always; # Prevents browser access to peripherals

5. Content Security Policy (CSP)

CSP is the most crucial defense against Cross-Site Scripting (XSS). Paperless-NGX’s UI uses inline scripts and styles, which complicate the policy.

The following CSP is a working compromise, allowing essential inline elements while blocking common injection points. I strongly suggest using the developer console to check for any blocked resources after implementation.

Nginx

# Functional CSP for paperless-ngx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src data: 'self'; upgrade-insecure-requests" always;

Note: Using 'unsafe-inline' is often necessary for applications that have not fully adopted modern CSP practices.

6. Blocking Search Engine Indexers (robots.txt)

Since this is a system for private documents, we must prevent all search engines and indexing services from crawling or indexing the instance, regardless of the login protection.

This is easily achieved in NGINX without creating a file on the disk:

Nginx

location = /robots.txt {
  add_header Content-Type text/plain;
  return 200 "User-agent: AdsBot-Google\nUser-agent: *\nDisallow: /\n";
}

Part IV: Final Site Configuration

The final NGINX site configuration combines all security requirements (HSTS, Headers, robots.txt) and correctly proxies to the secure loopback address.

Nginx

server {
  server_name documents.example.com;

  add_header Strict-Transport-Security "max-age=63072000" always;
  add_header Referrer-Policy "strict-origin-when-cross-origin";
  include conf.d/headers.conf; # Includes basic security headers

  location = /robots.txt {
    add_header Content-Type text/plain;
    return 200 "User-agent: AdsBot-Google\nUser-agent: *\nDisallow: /\n";
  }

  location / {
    proxy_pass http://localhost:8000/;

    # Required headers for secure proxying and WebSockets
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    # ... other proxy settings ...
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_redirect off;
  }
  
  # TLS Configuration
  listen 443 ssl; 
  ssl_certificate /etc/letsencrypt/live/.../fullchain.pem; 
  ssl_certificate_key /etc/letsencrypt/live/.../privkey.pem; 
  ssl_trusted_certificate /etc/letsencrypt/live/.../chain.pem;
}

# HTTP Redirect
server {
  listen 80;
  server_name documents.example.com;
  return 301 https://$host$request_uri;
}

Part V: Further Hardening Suggestions

To move beyond the basic secure setup, I suggest investigating these advanced hardening techniques:

AreaSuggestionGoal
AuthenticationExternal Authentication: Implement a proxy layer like Authelia or Keycloak to enforce Multi-Factor Authentication (MFA) before the Paperless-NGX login page.Zero Trust: Protect against Brute-Force attacks before they reach the application.
Rate LimitingFail2ban Integration: Configure Fail2ban to monitor NGINX access logs for login failures and automatically block the source IP.Brute-Force Defense at the network/IP layer.
Protocol SecurityDisable TLSv1.2: If all client devices are modern, disable TLSv1.2 completely to enforce TLSv1.3 only.Eliminate older, potentially vulnerable crypto-protocols.
Security HeadersStrong CORS Policies: Implement strict CORS headers (Cross-Origin Resource Sharing) to prevent the Paperless instance from being used to serve resources to unauthorized external domains.Defense against Cross-Origin attacks.

Sources / See Also

  1. Paperless-NGX Documentation. Installation Guide. https://docs.paperless-ngx.com/setup/
  2. Paperless-NGX Documentation. Advanced Tasks: Fail2ban. https://docs.paperless-ngx.com/advanced_tasks/#fail2ban
  3. Docker Documentation. Install Docker Engine. https://docs.docker.com/engine/install/debian/
  4. Mozilla SSL Configuration Generator. A reference tool for modern TLS configurations. https://ssl-config.mozilla.org/
  5. Scott Helme. Hardening Your HTTP Response Headers (X-Frame-Options, X-Content-Type-Options). https://scotthelme.co.uk/hardening-your-http-response-headers/
  6. Scott Helme. Content Security Policy – An Introduction. https://scotthelme.co.uk/content-security-policy-an-introduction/
  7. NGINX Documentation. Understanding the NGINX add_header Directive. http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header