Skip to main content

Refining the Proxy: Mastering Advanced Nginx Directives for High-Performance Services

As we expand our network architecture across hosts like Quince, Blackberry, and Plum, we inevitably encounter applications that demand more than a basic "pass-through" configuration. While the primary purpose of our Nginx gateway remains the consolidation of SSL management and the shielding of our internal IP range, certain high-demand services—specifically those involving media streaming, real-time automation, and large-scale data archiving—require us to tune the behavior of the proxy itself. By moving beyond standard headers and into the realm of advanced directives, we ensure that our infrastructure is not just a gatekeeper, but an optimized conduit for data. In this section of our manual, we will explore the specific technical requirements for Jellyfin, n8n, and BookStack, detailing how we utilize specialized Nginx commands to maintain synchronization, persistent connections, and seamless file handling.

The Transition to Wildcard SSL Management

A foundational shift in our current strategy is the move toward Let's Encrypt Wildcard Certificates. In our earlier iterations, we might have managed individual certificates for every subdomain, but as our service list grew to include jellyfin.seaoffate.net, n8n.seaoffate.net, and archive.seaoffate.net, the administrative burden became unsustainable. By utilizing a wildcard certificate—specifically the fullchain.pem and privkey.pem generated via a DNS-01 challenge—we provide a unified security blanket for the entire seaoffate.net domain. This approach allows us to spin up new services on any internal host and immediately secure them under our existing Nginx SSL blocks without the need to request new certificates or wait for propagation. This centralization of identity is the bedrock upon which our advanced service configurations are built.

Maintaining Stream Integrity: Disabling Buffering and Caching

One of the most critical adjustments we make for our media and automation services is the explicit disabling of Nginx’s native buffering and caching mechanisms. To understand why this is necessary for services like Jellyfin on Quince, we must look at how Nginx traditionally manages data. By default, Nginx acts as a "buffer" between the client and the backend server. When Quince sends a chunk of data, Nginx attempts to store it in memory until it has a substantial enough piece to send to the user's browser efficiently.

While this buffering is excellent for static text or small images, it is catastrophic for high-bitrate video streaming. In a Jellyfin environment, video and audio data must flow in a continuous, uninterrupted stream. If Nginx attempts to buffer these packets, it introduces micro-latencies that can lead to stuttering, buffering wheels on the client side, and, most frustratingly, a loss of synchronization between the audio and video tracks. By utilizing the directive proxy_buffering off;, we tell Nginx to treat the connection as a direct pipe. The moment a byte of data is received from the backend, it is immediately flushed to the client. This "Zero-Latency" approach ensures that our media playback remains fluid and synchronized, regardless of the file size or bitrate.

Similarly, we apply proxy_cache off; to these real-time services. While caching is a powerful tool for speeding up frequently accessed websites, it can cause significant issues in applications with dynamic user interfaces. For a service like n8n, where we are constantly editing workflows and viewing live execution data, we cannot risk Nginx serving us a "cached" version of the dashboard from ten seconds ago. We require the truth of the current state of the application at all times.

Enabling Persistent Communication via WebSockets

As we integrate modern, interactive tools like n8n and AnythingLLM, we must accommodate a communication protocol that goes beyond the traditional "request-response" cycle of HTTP. These applications rely on WebSockets to maintain a persistent, two-way connection between our browser and the backend server on Blackberry. This allows n8n to push live updates to our workflow canvas without us needing to refresh the page.

Nginx, being a high-performance HTTP server by design, requires explicit instructions to handle this protocol upgrade. We implement this through three specific directives:

  • proxy_http_version 1.1;

  • proxy_set_header Upgrade $http_upgrade;

  • proxy_set_header Connection "upgrade";

Without these lines, the Nginx proxy will attempt to treat every packet as a standard, independent HTTP request. When n8n attempts to "Upgrade" the connection to a WebSocket, Nginx will effectively drop the request or fail to pass the necessary headers to the backend. The result is an application that appears to load but remains "dead"—buttons won't respond, live logs won't appear, and we will frequently see "Connection Lost" popups. By configuring these "Upgrade" headers, we enable a robust, persistent tunnel that supports the real-time interactivity these modern tools require.

Accommodating Large-Scale Data Transfers

The final advanced directive we must document is the client_max_body_size. This is particularly relevant for our BookStack instance on Plum and our ArchiveBox setup on Blackberry. By default, Nginx limits client uploads to a mere 1MB. While this is a sensible security precaution to prevent "Buffer Overflow" or "Disk Exhaustion" attacks on a general-purpose web server, it is wholly insufficient for a technical wiki or a digital archive.

In our work on BookStack, we frequently upload high-resolution network diagrams, complex PDFs, and detailed screenshots that easily exceed the 1MB threshold. If we leave the default setting intact, Nginx will intercept these uploads and return a "413 Request Entity Too Large" error before the data even reaches Plum. By setting client_max_body_size 50M; (or higher for ArchiveBox), we provide the necessary "headroom" for our legitimate data transfers. This is a prime example of why we must understand our applications' requirements; the proxy should be an enabler of our work, not a bottleneck.

Summary: The Optimized Proxy Philosophy

In conclusion, the advanced directives we have implemented for our specialized services reflect a move away from "Standard Hosting" and toward "Application-Aware Infrastructure." We have recognized that a streaming server on Quince has fundamentally different needs than a documentation server on Plum. By mastering the use of buffering controls, protocol upgrades, and body-size limits, we have ensured that our Nginx gateway provides:

  1. Synchronization: Perfect audio/video alignment in Jellyfin.

  2. Persistence: Real-time, interactive workflows in n8n and AI tools.

  3. Flexibility: The ability to store and archive large-scale technical assets.

This "Defense in Depth" and "Optimization in Detail" strategy ensures that our network remains fast, secure, and—most importantly—perfectly suited to the specific tasks we demand of it.


Key Directive Reference Table

Directive Primary Application Technical Purpose
proxy_buffering off; Jellyfin / n8n Eliminates latency; prevents audio/video sync drift.
proxy_http_version 1.1 n8n / AnythingLLM Enables the persistent state required for WebSockets.
Upgrade / Connection n8n / AI Tools Hands off the protocol upgrade from HTTP to WS.
client_max_body_size BookStack / ArchiveBox Permits large file uploads for diagrams and backups.
proxy_cache off; All Real-time UIs Ensures we are seeing live data, not a stored snapshot.

Since we have already tackled the specialized needs of streaming and automation, there are several "infrastructure-level" directives that often fly under the radar until a specific problem arises. These directives focus on how Nginx manages the connections themselves—essentially the "plumbing" that keeps the water flowing when the pressure increases or when a backend server starts acting up.

For our environment, where we have multiple servers like Plum, Quince, and Blackberry communicating over a local network, the following directives are highly relevant for stability and security.


The "Ghost Backend" Fix: Mastering proxy_intercept_errors and Custom Error Handling

The purpose of this section is to detail the implementation of a professional error-handling layer within our Nginx gateway. In our current multi-server environment, we face a recurring aesthetic and security challenge: the "Leaked Error." When a backend server like Plum or Quince encounters an issue—whether it is a 404 Not Found, a 500 Internal Server Error, or a total system collapse resulting in a 502 Bad Gateway—the default behavior of Nginx is to simply pass that raw error page through to the visitor. This not only looks unprofessional, as it often displays raw Apache or PHP branding, but it also leaks information about our backend infrastructure. By utilizing the proxy_intercept_errors directive, we gain the ability to intercept these failures at the gateway and substitute them with our own branded, unified "Sea of Fate" maintenance and error pages.

The Core Directive: proxy_intercept_errors on;

By default, Nginx acts as a transparent window. If Plum returns a "404 Not Found," Nginx assumes the backend has already handled the situation and passes that 404 page directly to our browser. However, when we enable proxy_intercept_errors on;, we are telling Nginx to monitor the HTTP status codes coming back from the proxy. If the status code is 300 or higher (typically focusing on the 4xx and 5xx ranges), Nginx stops the transmission of the backend's response. Instead, it looks for a local instruction—the error_page directive—to determine what to show the user instead.

This is particularly vital for our "Silent Treatment" philosophy. If a backend server crashes, we don't want the user to see a generic "nginx/1.18.0 (Ubuntu)" error screen. We want them to see a page that fits our brand and provides a clear path forward, such as a link back to our main landing page or an "Expected Downtime" notice.

Defining Custom Error Pages: Global vs. Per-Domain

One of the most frequent questions we encounter in our documentation is whether we must define these error pages for every single subdomain (like notes, jellyfin, and n8n) or if we can set them once and forget them. The answer lies in the hierarchy of the Nginx configuration.

The Global Approach: Standardizing Our Infrastructure via Nginx Includes

The purpose of this section is to detail the most efficient method for implementing a universal "Maintenance and Error" strategy across our entire network. As our infrastructure grows to include multiple backend servers like Plum, Blackberry, and Quince, we must avoid the administrative burden of repeating the same error-handling code in every individual site configuration. Instead, we utilize the power of the Nginx include directive within our global http block. This allows us to define our "Sea of Fate" branding and error-interception logic in one central file, which is then automatically inherited by every subdomain under the *.seaoffate.net umbrella. This "Single Source of Truth" approach ensures that whether a visitor is trying to reach our notes, our media, or our automation tools, they receive a consistent and professional response if a backend service goes offline.

Why We Use the include Strategy

In a standard Nginx installation, the /etc/nginx/nginx.conf file acts as the master controller. While we could theoretically paste all our error logic directly into this file, doing so makes the master configuration cluttered and difficult to manage. By creating a separate file—for example, /etc/nginx/conf.d/global-errors.conf—we can keep our custom logic isolated.

When we "include" this file inside the http { ... } block of our main configuration, Nginx treats the contents of that file as if they were written directly into the master config. Because the http block is the parent of all server blocks, any directive placed here is inherited by every website we host. This means that if we ever want to change our maintenance message or add a new error code to catch (like a 403 Forbidden), we only have to edit one file and reload Nginx once to update our entire digital frontier.

Step 1: Creating Our Custom Error Include File

First, we create the configuration file that will hold our interception logic. We will place this in the conf.d directory, as Nginx is typically configured to load all .conf files from this location automatically, but we will explicitly include it to ensure total control.

File: /etc/nginx/conf.d/global-errors.conf

Nginx

# --- Global Error Interception for Sea of Fate ---

# Tell Nginx to intercept errors from all proxied backends
proxy_intercept_errors on;

# --- 1. Server-Side Failures (The "Backend is Dead" errors) ---
# 502 = Bad Gateway (Server Down)
# 503 = Service Unavailable (Maintenance)
# 504 = Gateway Timeout (Server Busy)
error_page 502 503 504 /seaoffate-maintenance.html;

# --- 2. Client-Side Failures (The "Broken Link" errors) ---
# 404 = Not Found
error_page 404 /seaoffate-404.html;

# --- 3. Access Failures (The "Security" errors) ---
# 403 = Forbidden
error_page 403 /seaoffate-403.html;

# --- Location Definitions for Each Page ---

# Maintenance / Offline Page
location = /seaoffate-maintenance.html {
    root /var/www/html/errors;
    internal;
    access_log off; 
}

# Not Found Page
location = /seaoffate-404.html {
    root /var/www/html/errors;
    internal;
}

# Forbidden Page
location = /seaoffate-403.html {
    root /var/www/html/errors;
    internal;
}

Step 2: Integrating the Include into nginx.conf

Now, we must ensure our master configuration file is aware of this new logic. We open /etc/nginx/nginx.conf and look for the http { ... } block. We place our include directive inside this block, usually near the end of the section but before the closing brace.

File: /etc/nginx/nginx.conf

Nginx

user www-data;
worker_processes auto;
pid /run/nginx.pid;

http {
    # ... standard settings for logging, Gzip, etc ...

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # --- OUR CUSTOM GLOBAL ERRORS ---
    # This line pulls in our interception logic for every site
    include /etc/nginx/conf.d/global-errors.conf;

    # Load our individual site configs (notes, jellyfin, etc.)
    include /etc/nginx/sites-enabled/*;
}

Step 3: Creating the Physical Error Pages

For this to function, we must create the corresponding HTML files on the gateway filesystem. We organize these in /var/www/html/errors/ to keep them separate from our primary application data.

  • seaoffate-maintenance.html: Provides a helpful message stating the backend (Plum, Quince, etc.) is undergoing work.

  • seaoffate-404.html: A clean page informing the user that the specific link they followed is broken or moved.

  • seaoffate-403.html: A security-focused page indicating that the user does not have permission to access that specific internal resource.

The Result: Inheritance and Defense in Depth

By structuring our configuration this way, we achieve a powerful "Waterfall" effect. When a request comes in for jellyfin.seaoffate.net and the Quince server happens to be rebooting, the following logic occurs:

  1. The server block for Jellyfin handles the request.

  2. The request is proxied to 192.168.100.75.

  3. Quince is down, so the connection fails, and the proxy returns a 502 Bad Gateway.

  4. Nginx looks at its settings and sees proxy_intercept_errors on; (inherited from our global include).

  5. Nginx sees that 502 is mapped to /seaoffate-maintenance.html.

  6. Nginx serves the branded HTML file from /var/www/html/errors.

Conversely, if a user hits notes.seaoffate.net/non-existent-page, the Plum server will return a 404. Nginx intercepts this and serves our custom seaoffate-404.html, ensuring the user stays within our branded environment even when they are lost.

The visitor never sees a "raw" Nginx error or an IP address; they only see the "Sea of Fate" maintenance screen. This applies automatically to Plum, Blackberry, and any future servers we add to the network. We no longer need to remember to add error-handling code every time we spin up a new service; the infrastructure is now "Secure and Professional by Default."

Summary: The Efficiency of the Global Approach

In conclusion, utilizing an include file within the global http block is the hallmark of a mature system architecture. It allows us to maintain a clean, readable master configuration while deploying complex security and aesthetic features across the entire domain. By standardizing our look, we ensure that:

  • Maintenance is Simplified: One file to edit for the entire network.

  • Consistency is Guaranteed: No subdomain is ever "left behind" with an ugly default error page.

  • Resource Management: We can disable logging for error pages globally, preventing our disks from filling up during a backend outage.

This approach transforms our gateway "Raisin" into a truly unified portal, providing a seamless experience for us and our users, even when the underlying hardware is undergoing essential work.


Implementation Checklist

Step Action Command/Location
1. Directory Ensure error directory exists sudo mkdir -p /var/www/html/errors
2. Content Place our branded HTML file /var/www/html/errors/seaoffate-maintenance.html
3. Config Create the global error config /etc/nginx/conf.d/global-errors.conf
4. Master Add the include to master file /etc/nginx/nginx.conf (within http block)
5. Reload Verify and apply sudo nginx -t && sudo systemctl reload nginx

The Per-Domain Approach (Specific Branding)

There are times when we might want a specific service to have a different error response. For instance, if our Jellyfin service is down, we might want to show an error page that specifically mentions "Media Server Maintenance," whereas for BookStack, we might want to suggest checking our "System Status" page. In this case, we place the directives inside the specific server { ... } block for that domain. The specific server block will always override the global HTTP settings, giving us granular control when we need it.

Crafting the Custom Error Page

To implement this, we must first create the physical HTML files on our Nginx gateway (Raisin). We typically store these in a dedicated directory like /var/www/html/errors/. We can create a simple, clean maintenance.html file:

HTML

<!DOCTYPE html>
<html>
<head>
    <title>Sea of Fate - Maintenance</title>
    <style>
        body { text-align: center; padding: 100px; font-family: sans-serif; background: #1a1a1a; color: #ddd; }
        h1 { font-size: 50px; color: #007bff; }
        p { font-size: 20px; }
    </style>
</head>
<body>
    <h1>System Maintenance</h1>
    <p>Our backend server is currently offline for scheduled updates.</p>
    <p>Please check back shortly or visit <a href="https://www.seaoffate.net" style="color:#007bff;">our homepage</a>.</p>
</body>
</html>

Implementing the Configuration

Once the file exists, we update our Nginx configuration to use it. Below is an example of how we would write this into our BookStack (Plum) configuration to handle both 502 (Bad Gateway) and 504 (Gateway Timeout) errors:

Nginx

server {
    listen 443 ssl;
    server_name notes.seaoffate.net;

    # The Interception Hook
    proxy_intercept_errors on;

    # Mapping specific codes to our custom file
    error_page 502 503 504 /custom_error.html;

    location / {
        proxy_pass https://192.168.100.22;
        # ... other proxy headers ...
    }

    # The Internal Redirect for the error page
    location = /custom_error.html {
        root /var/www/html/errors;
        internal; # Ensures users can't browse directly to this page
    }
}

Why We Use the internal Directive

In the configuration above, the location = /custom_error.html block includes the internal; command. This is a critical security and logic step. It tells Nginx that this location cannot be accessed directly by a user typing the URL into their browser. It can only be reached if Nginx itself "redirects" the user there as a result of a proxy_intercept_errors event. This prevents "Error Page Crawling" and ensures that our maintenance pages only appear when there is a genuine issue with the backend.

Summary: The Professional Edge

In conclusion, moving to proxy_intercept_errors on; is the final step in moving our network from a collection of home-lab scripts to a production-grade environment. We have moved beyond the "404 Not Found" and "502 Bad Gateway" screens of the 1990s and into a modern architecture where the user experience is preserved even during a failure. By centralizing our error pages, we ensure that:

  1. Brand Consistency: All failures look like they belong to the Sea of Fate network.

  2. Security: We no longer leak backend server types or versions through default error pages.

  3. Communication: We can provide users with helpful links or status updates during downtime.

This "Ghost Backend" fix ensures that even if our servers are invisible or offline, our gateway remains a professional and communicative front for our entire digital operation.


Implementation Cheat Sheet

Step Action Command/Path
1. Create Content Write the HTML error file /var/www/html/errors/502.html
2. Global Config Enable interception globally proxy_intercept_errors on; in nginx.conf
3. Link Codes Define which errors to catch error_page 502 503 /502.html;
4. Serve File Point Nginx to the file location location /502.html { root /var/www/html/errors; }
5. Reload Test and apply changes sudo nginx -t && sudo systemctl reload nginx

"

2. Preventing "Zombie" Connections: proxy_read_timeout

We have already optimized n8n with WebSockets, but for standard HTTP requests, Nginx has a default timeout (usually 60 seconds). If we are running a long-running process—like ArchiveBox trying to snapshot a massive website or AnythingLLM processing a huge document—the backend might take longer than 60 seconds to respond.

If the timeout hits, Nginx will abruptly kill the connection and show a "504 Gateway Timeout," even if the backend is still working hard. Adding proxy_read_timeout 300s; (5 minutes) gives our heavier applications the "breathing room" they need to finish intensive tasks without the proxy giving up on them.

3. The Performance Multiplier: proxy_set_header Connection ""

While this looks like we are setting the header to "nothing," it is actually a vital part of a feature called Keepalive.

By default, Nginx opens a new connection to Blackberry or Quince for every single request and closes it immediately after. This creates "TCP overhead." By adding proxy_set_header Connection ""; along with a proxy_http_version 1.1;, we tell Nginx to keep the internal connection open.

This is incredibly useful for Jellyfin. Instead of the servers constantly "saying hello" and "saying goodbye" for every tiny chunk of video data, they stay "on the line" with each other. This reduces CPU usage on our gateway and makes the entire network feel snappier.

4. Hiding Our Identity: server_tokens off;

This is a pure security directive. By default, if an error occurs, Nginx will happily tell the world exactly which version it is running (e.g., nginx/1.18.0 (Ubuntu)). This is a gift to attackers, as they can immediately look up known vulnerabilities for that specific version.

By placing server_tokens off; in our main nginx.conf (the global settings), we strip that information away. The error page will simply say "nginx," forcing an attacker to guess our version through much more difficult methods. It is a simple but effective part of our "Silent Treatment" philosophy.

5. Protecting Against "Slowloris" Attacks: client_body_timeout

While we have optimized the backend timeouts, we also need to worry about the client side. A "Slowloris" attack is when a bot sends data to us incredibly slowly, just to keep our connection slots full and prevent real users from getting in.

By setting client_body_timeout 10s; and client_header_timeout 10s;, we tell Nginx: "If a user starts sending a request but doesn't send the next piece within 10 seconds, drop them." Real users on a normal connection will never hit this, but it prevents bots from "clogging the pipes" of our gateway.


Summary Of the Less Obvious Directives

Directive Problem it Solves Why we need it
proxy_intercept_errors Ugly backend error pages Allows us to show custom "Maintenance" pages.
proxy_read_timeout 504 Gateway Timeouts Needed for long AI or Archiving tasks.
proxy_set_header Connection "" High internal TCP overhead Makes internal communication significantly faster.
server_tokens off; Information leaking Hides our Nginx version from potential attackers.
client_body_timeout Connection clogging (DDoS) Ensures bots can't hang onto our connections forever.

Advanced Nginx Orchestration: Mastering the "Invisible" Infrastructure Directives

As our network architecture matures across hosts like Quince, Blackberry, and Plum, we move beyond the basic task of simply "pointing a domain at an IP." To achieve a truly professional-grade environment, we must employ specific Nginx directives that manage the finer details of connection stability, log cleanliness, and backend security. These "lesser-known" directives are the difference between a functional proxy and an intelligent gateway. In this section of our manual, we will explore the directives that allow us to shape traffic, hide our internal signatures, and protect our gateway's physical resources.

1. The "Clean Log" Directive: log_not_found

Our gateway, "Raisin," is constantly hit by automated bots searching for standard files like apple-touch-icon.png, favicon.ico, or robots.txt. If these files do not exist for every single subdomain (such as jellyfin.seaoffate.net or notes.seaoffate.net), Nginx will default to writing a "404 Not Found" entry into the error log for every single attempt. Over time, this creates "log noise" that makes it nearly impossible to find genuine system errors on our backend servers.

By adding log_not_found off;, we tell Nginx to serve the 404 error to the bot but to remain silent in our internal logs.

Implementation Example:

Nginx

server {
    listen 443 ssl;
    server_name notes.seaoffate.net;
    
    # Silence the noise of missing icons/robots files
    log_not_found off;

    location / {
        proxy_pass https://192.168.100.22;
    }
}

2. The "Resource Protector": limit_req (Rate Limiting)

While our pfSense firewall handles broad network-level blocks, Nginx can provide "Application-Level" protection. If a single IP address begins hammering n8n.seaoffate.net with hundreds of requests per second, it could exhaust the processing power of the n8n engine on Blackberry. We use a "leaky bucket" algorithm to ensure that no single user can "starve" our other services of resources.

First, we define a "zone" in our global http block, and then we apply the limit to our specific service.

Implementation Example:

Nginx

# In /etc/nginx/nginx.conf (http block)
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

# In our service config
server {
    server_name n8n.seaoffate.net;
    
    location / {
        # Limit to 10 requests per second with a "burst" of 20
        limit_req zone=mylimit burst=20 nodelay;
        proxy_pass http://192.168.100.85:5678;
    }
}

3. The "Header Scrub": proxy_hide_header

Many backend applications are inherently "talkative." A Docker container or a PHP application on Plum might send headers like X-Powered-By: PHP/8.2 or Server: Apache/2.4.41. Even if we have server_tokens off; enabled on our Nginx gateway, these backend signatures can leak through to the user's browser, providing a roadmap of our internal software versions to potential attackers.

To maintain our "Silent Treatment" philosophy, we use the proxy_hide_header directive to strip these signatures at the gateway before the packet ever leaves our network.

Implementation Example:

Nginx

location / {
    proxy_pass https://192.168.100.22;
    
    # Scrub the backend's identity
    proxy_hide_header X-Powered-By;
    proxy_hide_header Server;
}

4. The "Large Response" Handler: proxy_max_temp_file_size

When we use services like ArchiveBox or Jellyfin, the backend server might occasionally send a very large file. If Nginx has buffering enabled, it will attempt to save that file to its own local disk (usually in /var/lib/nginx/proxy) before passing it to the user. If our gateway is running on a small SSD or a Micro-SD card, a single large download could fill the disk and crash the entire proxy service.

By setting proxy_max_temp_file_size 0;, we disable this temporary disk-buffering. This forces Nginx to stream the data directly from the backend memory to the user, effectively bypassing the gateway's storage.

Implementation Example:

Nginx

server {
    server_name jellyfin.seaoffate.net;
    
    location / {
        proxy_pass http://192.168.100.75:8096;
        
        # Disable disk-buffering to protect the gateway's SSD
        proxy_max_temp_file_size 0;
    }
}

5. The "Domain Mirror": proxy_redirect

Sometimes a backend application on Plum might issue a redirect (such as a 301 login redirect) that points to its own internal IP address (e.g., http://192.168.100.22/login). If Nginx passes this redirect "as is," our browser will attempt to connect to that private internal IP and fail.

The proxy_redirect directive ensures the "Illusion of the Proxy" remains intact by automatically rewriting those internal IP redirects to our public domain.

Implementation Example:

Nginx

location / {
    proxy_pass https://192.168.100.22;
    
    # Automatically rewrite backend redirects from 192.168.100.22 to notes.seaoffate.net
    proxy_redirect default;
    
    # Manual example if the app is being stubborn:
    # proxy_redirect http://192.168.100.22/ https://notes.seaoffate.net/;
}

Summary Table: Advanced Directives for Our Infrastructure

Directive Configuration Value Problem it Solves
log_not_found off; Prevents "favicon/robots" noise from clogging error logs.
limit_req zone=name burst=20; Prevents a single IP from overwhelming a backend service.
proxy_hide_header Header-Name; Scrubs backend version info (PHP/Apache) for security.
proxy_max_temp_file_size 0; Prevents large file transfers from filling up the gateway disk.
proxy_redirect default; Corrects broken redirects that point to internal IPs.
Gemini said

Orchestrating the Digital Frontier: A Unified Guide to Advanced Proxy Directives

As we consolidate our infrastructure across various dedicated hosts like Quince, Blackberry, and Plum, we have moved beyond simple web hosting into the realm of complex service orchestration. Our Nginx gateway, Raisin, no longer acts as a mere traffic cop; it has become an intelligent mediator that understands the specific linguistic and performance requirements of every application in our stack. From the high-bitrate demands of Jellyfin to the persistent, real-time workflows of n8n, each service requires a tailored set of Nginx directives to ensure security, stability, and a seamless user experience. In this summary, we will detail the specialized configurations we have implemented for our most critical services, exploring why certain "non-standard" commands are the difference between a broken connection and a professional-grade deployment.

The Real-Time Imperative: Jellyfin and n8n

The most significant departure from standard web serving occurs when we handle media streaming and live automation. For Jellyfin (hosted on Quince) and n8n (hosted on Blackberry), the default behavior of Nginx—buffering—becomes a primary point of failure. By default, Nginx attempts to collect data from the backend in its own memory before sending it to the client, a process designed to maximize throughput for static files. However, for a video stream or a live automation canvas, this introduces fatal latency.

In our Jellyfin configuration, we explicitly set proxy_buffering off; and proxy_cache off;. Without these, Nginx would attempt to "chunk" the video stream, leading to stuttering playback and the dreaded loss of synchronization between audio and video tracks. By forcing Nginx to act as a direct, unbuffered pipe, we ensure that the high-bitrate media from Quince reaches our devices with zero interference. We apply the same logic to n8n to prevent "Connection Lost" popups. Because n8n relies on frequent, tiny "heartbeat" packets to maintain the state of the web interface, any buffering by the proxy would delay these packets, causing the browser to assume the backend has crashed.

Furthermore, n8n and AnythingLLM require the transition from standard HTTP to WebSockets. This is achieved through the "Upgrade" handshake:

Nginx


proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

These directives allow our browser to maintain a persistent, two-way tunnel with the backend. For AnythingLLM, this is what allows the AI to "stream" its response word-by-word into our chat interface; for n8n, it allows the workflow nodes to light up in real-time as they execute. Without this protocol upgrade, these modern, interactive applications would simply fail to load their core interfaces.

Handling Large-Scale Data: BookStack, ArchiveBox, and Nextcloud

When we turn our attention to our documentation and storage engines—BookStack on Plum, and ArchiveBox or Nextcloud on our storage nodes—the primary challenge shifts from latency to volume. By default, Nginx imposes a strict 1MB limit on client uploads. For a technical wiki like BookStack, where we frequently upload high-resolution network diagrams, or for Nextcloud, where we may be syncing multi-gigabyte files, this limit is an immediate bottleneck.

We resolve this by implementing client_max_body_size. For BookStack, we typically set this to 50M to accommodate even the largest documentation assets. For Nextcloud, however, we often push this much higher or set it to 0 (unlimited) to allow for massive file backups. Along with this, we utilize proxy_max_temp_file_size 0;. This is a critical "Resource Protector" for our gateway, Raisin. Without it, Nginx would try to save a 2GB Nextcloud upload to its own local disk before passing it to the backend. If Raisin’s disk space is limited, this would cause the entire proxy to crash. By disabling temp-file buffering, we force the data to stream directly through memory, protecting our gateway's physical storage.

Specialized Privacy and Performance: Piwigo and Wikimedia

For our image-heavy and collaborative services like Piwigo (photo management) and Wikimedia, we focus on "Header Sanitization" and "Redirect Correction." Applications like Wikimedia are notoriously sensitive to their environment; they often try to issue redirects to their internal IP addresses or specific ports. We use proxy_redirect default; to ensure that if Wikimedia on the backend tries to send a user to an internal 192.168.100.x address, Nginx catches it and rewrites it to the public wikimedia.seaoffate.net address.

In Piwigo, we often deal with "talkative" headers. PHP-based photo galleries frequently leak information about the underlying server via headers like X-Powered-By. To maintain our "Silent Treatment" security posture, we employ proxy_hide_header X-Powered-By; and proxy_hide_header Server;. This ensures that even if our backend servers are running different versions of Apache or PHP, the outside world only sees a clean, uniform Nginx response from the Sea of Fate gateway.

The Defensive Layer: Global Health and Timeouts

Across all these services, we have implemented a suite of "Infrastructure Hardening" directives that protect the entire network. The most important of these is proxy_intercept_errors on;, which we have integrated via a global include. This ensures that if Plum or Quince goes offline, the user is not met with a generic "Bad Gateway" screen but instead sees our branded "Maintenance in Progress" page.

We also utilize proxy_read_timeout 300s; for our AI and Archiving tasks. Services like AnythingLLM or ArchiveBox can take several minutes to process a complex request. If we left the timeout at the default 60 seconds, Nginx would kill the connection while the backend was still working, leading to a "504 Gateway Timeout." By extending this window, we accommodate the heavy-lifting nature of our specialized AI and data-preservation tools.


Summary Table: Our Specialized Application Directives

Application Primary Directive(s) Impact on Performance/Security
Jellyfin proxy_buffering off; Prevents audio/video desync and stuttering.
n8n Upgrade / Connection Enables real-time WebSocket communication.
Nextcloud proxy_max_temp_file_size 0; Protects gateway disk from large file syncs.
BookStack client_max_body_size 50M; Allows for large diagram and document uploads.
AnythingLLM proxy_read_timeout 300s; Prevents timeouts during long AI computations.
ArchiveBox client_max_body_size 0; Allows for comprehensive web snapshots.
Piwigo proxy_hide_header Scrubs backend software signatures (PHP/Apache).
Global proxy_intercept_errors on; Provides a unified "Maintenance" experience.

Conclusion: The Intelligent Gateway

In conclusion, our Nginx configuration on Raisin has evolved into a sophisticated management layer that respects the unique "personality" of every service we host. We have moved beyond the "Standard Proxy" and into a tiered architecture where we prioritize different metrics—latency for Jellyfin, persistence for n8n, and volume for Nextcloud—without compromising our central security philosophy. By mastering these less common directives, we have ensured that our "Sea of Fate" network is not just a collection of apps, but a cohesive, resilient, and professional digital ecosystem.