If you’ve ever dealt with Nginx and its dynamic modules, you know the drill. An Nginx package update hits, and suddenly your custom modules – like ModSecurity or GeoIP2 – are no longer compatible. The whole process is a headache: you have to stop Nginx, recompile your modules against the new version, copy the files, and restart the service.
I was looking for a way to automate this. The goal was simple: ensure that dynamic modules are always compatible with a new Nginx version. And if the recompilation fails for any reason, the entire Nginx update must be aborted.
This article documents a reliable update script for the Paperless-NGX stack, which minimizes the risk of container failures during automated maintenance. The focus here is not just on simple automation, but on ensuring the integrity of the process—especially handling logs and exit codes within complex Bash pipelines.
If you follow current IT security vulnerabilities, you’ll agree that keeping systems up to date is critical. Unattended Upgrades for Debian/Ubuntu offers a simple yet powerful way to automate this process, securing your infrastructure with minimal manual intervention.
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, unprivilegedpaperless user by leveraging sudo -Hu paperless.
Necessary for reverse-proxy and SSL configuration.
Database backend
postgres
Recommended for production and better performance compared to SQLite.
Enable Apache Tika?
yes
Required for indexing complex document types (Word, Excel, PowerPoint).
OCR language
deu+eng+fra+ara
Caution: 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
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 oneadd_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:
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.
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:
The final NGINX site configuration combines all security requirements (HSTS, Headers, robots.txt) and correctly proxies to the secure loopback address.
To move beyond the basic secure setup, I suggest investigating these advanced hardening techniques:
Area
Suggestion
Goal
Authentication
External 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 Limiting
Fail2ban 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 Security
Disable TLSv1.2: If all client devices are modern, disable TLSv1.2 completely to enforce TLSv1.3 only.
Strong 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.
Mozilla SSL Configuration Generator. A reference tool for modern TLS configurations. https://ssl-config.mozilla.org/
Scott Helme. Hardening Your HTTP Response Headers (X-Frame-Options, X-Content-Type-Options). https://scotthelme.co.uk/hardening-your-http-response-headers/
Scott Helme. Content Security Policy – An Introduction. https://scotthelme.co.uk/content-security-policy-an-introduction/
NGINX Documentation. Understanding the NGINX add_header Directive. http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
This article documents a two-part process: successfully upgrading Suricata to version 7 on Debian Bookworm and solving a critical stability issue required to run the AF-Packet IPS mode with high-performance VirtIO NICs in a virtual machine. Without this specific configuration, the IPS failed to function.
Part I: Suricata 7 Upgrade and Policy Changes
A much newer Suricata version can be installed by utilizing Debian’s bookworm-backports repository, which is essential for access to the latest security features and performance enhancements.
The Backports Installation
Ensure the backports repository is configured in your /etc/apt/sources.list:
deb https://ftp.debian.org/debian/ bookworm-backports contrib main non-free non-free-firmware
Install Suricata using the specific target:
apt-get install -t bookworm-backports suricata
Post-Upgrade Security Alert (Critical)
After upgrading to Suricata 7, you may experience immediate traffic blocking. This is not a bug, but a deliberate change in the application’s default security posture.
Reason: Suricata 7 introduced new policy rules that are often set to drop by default.
Action: You must review your new suricata.yaml configuration. The recommended approach is to install the new configuration files, compare them with your old setup, and set unwanted policies to ignore.
Reference: This new behavior is explicitly documented in the official Suricata 7 Changelog. Consult the Suricata FAQ for troubleshooting details on blocking issues.
Part II: The VirtIO and AF-Packet Critical Failure Fix
When using Suricata in IPS mode with the high-performance AF-Packet acquisition method, using VirtIO NICs is preferred. However, without a specific Libvirt configuration, the IPS fails entirely to process bridged traffic.
The Problematic Default VirtIO Config
If the VirtIO NIC is defined simply with <model type='virtio'/> in the Libvirt XML, AF-Packet fails to initialize or correctly process traffic.
The Solution: Disabling Guest Checksum Offload
The fix requires overriding the default driver settings by introducing the <driver> block and explicitly setting checksum (csum) offloading to off for the guest system.
This solution was found while troubleshooting similar packet loss issues in a thread related to XDP drivers in RHEL environments, suggesting a common kernel/driver interaction problem with aggressive offloading features.
The minimal required working Libvirt XML configuration looks like this:
Crucial Insight: The key fix is the parameter csum='off' within the <guest/> tag. If checksum offloading is left enabled (csum='on'), the system fails to bridge traffic completely.
Part III: The Deep Dive: Why Checksum Offload Causes Complete Failure
Here is the rationale for why Checksum Offload (CSUM) leads to complete non-functionality:
1. The CSUM Optimization Paradigm (CSUM=’on’)
When you set csum='on', you are performing a performance optimization aimed at saving CPU cycles:
The Host/Hypervisor receives packets and passes them to the VirtIO Driver (Vhost).
The Vhost Driver passes the packets into the VirtIO Ring in the Guest System, but marks them with a special flag (e.g., in the skb—Socket Buffer—metadata) signaling to the Guest Kernel: “Attention, the L3/L4 checksum is invalid/missing and must be corrected or calculated before further processing up the stack.”
This is a performance trick: the CPU-intensive checksum calculation is delegated to the Guest Kernel, but only when it is truly necessary.
2. The Collision Point: AF-Packet Bypass
Suricata using AF-Packet now bypasses precisely this process:
AF-Packet is a very low-level packet capture method. It operates directly above the driver (or in the kernel) and fetches the raw L2 frames directly from the VirtIO Ring.
Suricata receives the packet at a point before the standard kernel stack has performed the checksum finalization.
Suricata’s Deep Packet Inspection (DPI) engine relies on the integrity of the Layer 3/Layer 4 headers (e.g., to check the TCP segment length, track the TCP state machine, or evaluate the validity of IP headers).
The Non-Functionality: Since Suricata receives a packet with the “Checksum missing/invalid” flag, it interprets this not as an optimization instruction, but as a critical error in the packet itself (Corrupted Packet).
3. The Resolution (CSUM=’off’)
By explicitly setting <guest csum='off'>, we force the Host/Vhost Driver to deliver the packets to the Guest as if they were ‘normal’ Ethernet frames that already contain all checksums. Suricata therefore only sees complete, consistent packets and can apply the DPI logic without error.
Sources / See Also
Suricata Documentation. Suricata 7 Changelog (Note new policy behavior). https://suricata.io/changelog/
Suricata Documentation. FAQ: Traffic gets blocked after upgrading to Suricata 7. https://suricata-update.readthedocs.io/en/latest/faq.html#my-traffic-gets-blocked-after-upgrading-to-suricata-7
Suricata Documentation. Working with AF-Packet. https://docs.suricata.io/en/latest/install/af-packet.html
This is a follow-up to my last post in which I set up Suricata as an IPS. This article demonstrates how to effectively work with the Suricata engine—specifically, how I analyze its log output, silence unnecessary alerts, and promote specific detection rules to prevention rules.
A very light-weight and efficient approach for consolidating logs centrally is by using rsyslog. My virtual machines all use rsyslog to forward their logs to a dedicated internal virtual machine, which acts as the central log hub. A fail2ban instance on this hub checks all incoming logs and sends a block command to an external firewall—a process helpful for automated security.
Short explanation on how to get the Nextcloud Linux desktop client working reliably on a Chromebook. This solution is necessary because the official Android desktop client does not offer true two-way synchronization, which is a critical feature for managing files across systems.
I guess that most people use amavisd-new together with spamassassin and for example ClamAV. Probably a few more use features like DKIM verification and signing with amavis. However, there are some features which aren’t found in the usual howtos. Here are some of them.
Instead of running a single global Redis server, I prefer to use multiple isolated instances. This allows me to precisely limit resources like memory (maxmemory) and apply specific tuning per instance. This approach is fundamental to reliable operation in a shared environment. I used systemd templates to manage this, creating an instance for Amavisd-new as a practical example.