<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Nginx - Tag - arleo.eu</title><link>https://www.arleo.eu/en/tags/nginx/</link><description>Nginx - Tag - arleo.eu</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Fri, 29 May 2026 20:30:00 +0200</lastBuildDate><atom:link href="https://www.arleo.eu/en/tags/nginx/" rel="self" type="application/rss+xml"/><item><title>Hugo SEO: Googlebot 404s, noindex aliases and sitemap normalization</title><link>https://www.arleo.eu/en/posts/debug-seo-404-broken-links/</link><pubDate>Fri, 29 May 2026 20:30:00 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/debug-seo-404-broken-links/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/debug-seo-404-broken-links-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="context">Context</h2>
<p>Google Search Console was reporting four categories of issues on arleo.eu:</p>
<ul>
<li><strong>Googlebot 404s</strong>: <code>/fr/tag/cloudflare</code>, <code>/en/tag/nginx</code>, <code>/fr/tag/javascript</code>… URLs with <code>/fr/</code> prefix or singular <code>/tag/</code> never served by nginx</li>
<li><strong>16 &ldquo;Excluded by noindex tag&rdquo; pages</strong>: all redirect pages generated by <code>aliases:</code> in Hugo frontmatter</li>
<li><strong>Robots tag</strong>: <code>noodp</code> hardcoded in the LoveIt theme</li>
<li><strong>FR/EN sitemap</strong>: 104 vs 105 URLs — a duplicate FR tag and two missing tags</li>
</ul>
<hr>
<h2 id="act-1-hugo-aliases--nginx-301-redirects">Act 1: Hugo aliases → nginx 301 redirects</h2>
<h3 id="why-hugo-generates-noindex-pages">Why Hugo generates noindex pages</h3>
<p>Hugo generates <code>aliases:</code> frontmatter entries as static HTML files:</p>]]></description></item><item><title>CSP A+ on Hugo + Cloudflare: from hash-based to origin allowlist, auto-monitoring and hardening</title><link>https://www.arleo.eu/en/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</link><pubDate>Thu, 28 May 2026 23:45:41 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/csp-a-plus-hugo-cloudflare-origin-allowlist/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/csp-a-plus-hugo-cloudflare-origin-allowlist-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="context">Context</h2>
<p>arleo.eu runs on Hugo (KVM VM) → OpenResty (NUC) → Cloudflare (CDN/WAF). Goal: <strong>A+ score on Mozilla Observatory</strong> with a strict CSP that resists edge-side injections.</p>
<p>The journey went through three strategies over a few weeks — nonces, hashes, then origin allowlist — before landing on a stable, automated solution.</p>
<hr>
<h2 id="act-1-why-hash-based-failed">Act 1: why hash-based failed</h2>
<p>The initial idea seemed solid: Hugo Pipes externalizes all JS with SRI, we list the hashes in the CSP, clean result. In practice, two problems made the approach impossible.</p>]]></description></item><item><title>Postmortem — CrowdSec AppSec: Heuristic False Positive on Sonarr/Radarr</title><link>https://www.arleo.eu/en/posts/postmortem-crowdsec-appsec-false-positive-sonarr/</link><pubDate>Mon, 25 May 2026 20:51:17 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/postmortem-crowdsec-appsec-false-positive-sonarr/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/postmortem-crowdsec-appsec-false-positive-sonarr-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="summary">Summary</h2>
<p>On <strong>May 25, 2026 at around 10:02 PM (local time)</strong>, Sonarr and Radarr became completely inaccessible from the home IP (<code>82.XX.XX.XX</code>), returning <strong>403</strong> on every URL including <code>/login</code>. The service was fully operational. Initial suspicion fell on the day&rsquo;s <code>crowdsec-cf-sync</code> refactor deployment — the real cause was a <strong>CrowdSec AppSec heuristic false positive</strong>.</p>
<hr>
<h2 id="timeline">Timeline</h2>
<table>
  <thead>
      <tr>
          <th>Time (local)</th>
          <th>Event</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>~10:00 PM</td>
          <td>Sonarr browser session cookie expired</td>
      </tr>
      <tr>
          <td>10:02:31 PM</td>
          <td>Browser loads Sonarr library → attempts to fetch 20+ <code>/MediaCover/*.jpg</code> simultaneously</td>
      </tr>
      <tr>
          <td>10:02:31 PM</td>
          <td>Sonarr returns 302 → <code>/login</code> for each image (invalid session)</td>
      </tr>
      <tr>
          <td>10:02:34 PM</td>
          <td>SignalR WebSocket connection succeeds (101) via <code>access_token</code> in URL</td>
      </tr>
      <tr>
          <td>~10:05 PM</td>
          <td>CrowdSec AppSec triggers heuristic rule <code>http-probing</code>: burst of failed requests from same IP</td>
      </tr>
      <tr>
          <td>10:11:37 PM</td>
          <td>All requests from <code>82.XX.XX.XX</code> return 403 — <code>cs_reason=heuristic</code> in nginx logs</td>
      </tr>
      <tr>
          <td>10:13:34 PM</td>
          <td>Even <code>/login</code> is blocked — IP cannot authenticate</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="root-cause">Root Cause</h2>
<p>CrowdSec AppSec maintains an <strong>in-memory heuristic state</strong>, separate from LAPI decisions. When the browser simultaneously tries to load many resources and receives 302/403 from the upstream application (Sonarr), AppSec interprets the burst of failures as aggressive probing (<code>http-probing</code>) and blocks the source IP.</p>]]></description></item><item><title>CrowdSec AppSec + OpenResty: Modern WAF Without ModSecurity</title><link>https://www.arleo.eu/en/posts/crowdsec-appsec-openresty/</link><pubDate>Mon, 18 May 2026 00:07:55 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/crowdsec-appsec-openresty/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/crowdsec-appsec-openresty-featured.jpg" referrerpolicy="no-referrer">
            </div><p>After years running ModSecurity + OWASP CRS on nginx, I migrated arleo.eu to a more modern stack: <strong>CrowdSec AppSec on OpenResty</strong>. The result is a tighter inline WAF architecture — better integrated, easier to maintain, and fully coherent with the rest of the security stack.</p>
<h2 id="why-drop-modsecurity">Why Drop ModSecurity?</h2>
<p>ModSecurity v2 is in maintenance mode. Managing OWASP CRS rules on classic nginx generates friction: frequent false positives, logs that are hard to correlate with CrowdSec, and a configuration spread across multiple tools with no unified view.</p>]]></description></item><item><title>CSP Hash on Hugo: migrating from nonce to hash to preserve CDN cache</title><link>https://www.arleo.eu/en/posts/csp-hash-hugo/</link><pubDate>Sat, 16 May 2026 00:00:00 +0000</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/csp-hash-hugo/</guid><description>&lt;div class="featured-image">
                &lt;img src="/images/csp-hash-hugo-featured.png" referrerpolicy="no-referrer">
            &lt;/div>CSP nonces and CDN caching are incompatible by design. On a Hugo static site, SHA-256 hashes are the native approach: computed at build time, stable across requests, and fully compatible with Cloudflare caching.</description></item><item><title>NUC Security Audit: ModSecurity Removed, 500 MB Recovered</title><link>https://www.arleo.eu/en/posts/audit-securite-modsecurity-crowdsec/</link><pubDate>Thu, 14 May 2026 05:32:19 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/audit-securite-modsecurity-crowdsec/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/audit-securite-modsecurity-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-tldr">⚡ TL;DR</h2>
<p>A security stack audit on the homelab NUC reveals <strong>redundant double WAF inspection</strong>: ModSecurity + OWASP CRS load 11,872 rules into memory despite <code>SecRuleEngine Off</code>, running in parallel with CrowdSec AppSec which already covers the same surface. After removing the ModSecurity nginx module and five other targeted fixes, nginx drops from <strong>~520 MB to ~27 MB PSS</strong>. Same security, memory footprint divided by 20.</p>
<hr>
<h2 id="-architecture-before-the-audit">🏗️ Architecture Before the Audit</h2>
<p>The security stack had six stacked layers:</p>]]></description></item><item><title>Hugo MCP Server: Connecting Claude.ai to a Static Hugo Site</title><link>https://www.arleo.eu/en/posts/hugo-mcp-server/</link><pubDate>Sun, 03 May 2026 19:00:00 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/hugo-mcp-server/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-mcp-server-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-in-short">⚡ In short</h2>
<p>Connect Claude.ai to a <strong>Hugo</strong> site hosted in a KVM VM in 30 minutes: a FastAPI server exposes 6 MCP tools (read, create, modify, delete pages, rebuild the site) via JSON-RPC 2.0, an OAuth proxy reuses existing infrastructure, and every modification automatically triggers a Hugo rebuild + Cloudflare cache purge.</p>
<p>The code is available on GitHub:</p>
<ul>
<li>🔌 Hugo MCP Server: <a href="https://github.com/jmrGrav/hugo-mcp" target="_blank" rel="noopener noreffer ">jmrGrav/hugo-mcp</a></li>
<li>🔐 OAuth Proxy: <a href="https://github.com/jmrGrav/mcp-oauth-proxy" target="_blank" rel="noopener noreffer ">jmrGrav/mcp-oauth-proxy</a></li>
</ul>
<h2 id="-why">🧠 Why</h2>
<p>Anthropic&rsquo;s <a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreffer ">MCP (Model Context Protocol)</a> allows Claude.ai to connect to external data sources via standardized tools. Unlike Grav CMS which is dynamic (PHP), Hugo generates pure static HTML — making content management via MCP even more powerful: every modification is compiled and deployed instantly.</p>]]></description></item><item><title>KVM Media VM: migrating Sonarr, Radarr and SABnzbd into an isolated VM</title><link>https://www.arleo.eu/en/posts/media-vm-migration/</link><pubDate>Sun, 03 May 2026 18:00:00 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/media-vm-migration/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/media-vm-migration-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-tldr">⚡ TL;DR</h2>
<p>Migrating the media stack (Sonarr, Radarr, SABnzbd) into a dedicated KVM VM running Ubuntu 24.04: security isolation, easy snapshots, QNAP NFS mounted inside the VM, nginx reverse proxy on the host. The migration fully preserves SQLite databases and existing configuration.</p>
<p><strong>Target stack:</strong></p>
<ul>
<li>🖥️ <strong>Host</strong>: NUC8i3BEH, Ubuntu Server, nginx reverse proxy, Plex (GPU transcoding)</li>
<li>📦 <strong>VM media-vm</strong>: Ubuntu 24.04, 2 vCPU, 8 GB RAM, 120 GB (X5 NVMe), QNAP NFS</li>
<li>🎬 <strong>Migrated services</strong>: Sonarr (port 8989), Radarr (port 7878), SABnzbd (port 6789)</li>
</ul>
<h2 id="-why-isolate-the-media-stack-in-a-vm">🧠 Why isolate the media stack in a VM</h2>
<p>Sonarr, Radarr and SABnzbd present a significant attack surface: network calls to external indexers, post-download script execution, filesystem access to the media library. Confining them in a VM provides:</p>]]></description></item><item><title>Hugo on KVM: Installing an Ubuntu VM for a Static Site</title><link>https://www.arleo.eu/en/posts/hugo-vm-installation/</link><pubDate>Sun, 03 May 2026 09:00:00 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/hugo-vm-installation/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/hugo-vm-installation-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-in-short">⚡ In short</h2>
<p>Progressive migration from <strong>Grav CMS</strong> to <strong>Hugo</strong> — a static site generator. The goal is to isolate Hugo in a dedicated KVM VM on the NUC8i3BEH, with nginx on the host as a reverse proxy. The generated static site is served by nginx inside the VM — no PHP, no database, no application attack surface.</p>
<ul>
<li>🖥️ <strong>Host</strong>: NUC8i3BEH Ubuntu 24.04 — nginx proxy + KVM</li>
<li>🗄️ <strong>VM disk</strong>: Samsung X5 external NVMe (exFAT → ext4 loop image)</li>
<li>🌐 <strong>Access</strong>: <a href="https://hugo-test.arleo.eu" target="_blank" rel="noopener noreffer ">hugo-test.arleo.eu</a></li>
<li>🎨 <strong>Theme</strong>: <a href="https://github.com/dillonzq/LoveIt" target="_blank" rel="noopener noreffer ">LoveIt</a></li>
</ul>
<h2 id="-why">🧠 Why</h2>
<p>Grav CMS is excellent but relies on PHP — a non-negligible attack surface. Hugo generates pure static HTML: no PHP, no database, no application vulnerability. Performance is also radically better — HTML is served directly by nginx without dynamic processing.</p>]]></description></item><item><title>From 46 Hashes to Zero: Migrating CSP to Dynamic Nonces</title><link>https://www.arleo.eu/en/posts/csp-nonce/</link><pubDate>Wed, 15 Apr 2026 13:22:00 +0200</pubDate><author>Jmr</author><guid>https://www.arleo.eu/en/posts/csp-nonce/</guid><description><![CDATA[<div class="featured-image">
                <img src="/images/csp-nonce-featured.jpg" referrerpolicy="no-referrer">
            </div><h2 id="-in-short">⚡ In short</h2>
<p>The original CSP listed 46 SHA-256 hashes to cover inline scripts and styles — unmanageable, fragile, and approaching Cloudflare&rsquo;s 4,096 character limit. This migration to <strong>dynamic nonces</strong> reduces the CSP to ~600 characters, eliminates all manual hash maintenance, and adds structured violation reporting in BetterStack.</p>
<p>The Grav plugin powering this is available on GitHub:
🔌 Plugin : <a href="https://github.com/jmrGrav/grav-plugin-csp-nonce" target="_blank" rel="noopener noreffer ">jmrGrav/grav-plugin-csp-nonce</a></p>
<h2 id="-why">🧠 Why</h2>
<p>When implementing a strict Content Security Policy on a Grav CMS site served through Cloudflare, the naive approach is to list SHA-256 hashes for every inline script. It works, but quickly becomes unmanageable. Several compounding problems:</p>]]></description></item></channel></rss>