<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Bug: network blocklist is flushed and refilled line by line — the firewall is partly open for minutes on every update (fix included)]]></title><description><![CDATA[<p dir="auto">While investigating hourly API timeouts on <code>POST /api/v1/network/blocklist</code> I found that every blocklist update leaves the server partly unprotected for several minutes. I measured it, read the code (10.1.5, unchanged in master), and wrote a drop-in replacement for <code>setblocklist.sh</code> that fixes it. I think this is also the root cause behind several older threads.</p>
<p dir="auto"><strong>What happens</strong></p>
<p dir="auto"><code>src/scripts/setblocklist.sh</code> first runs <code>ipset flush cloudron_blocklist</code> and then re-adds every line with a separate <code>ipset add</code> process. With a list of ~110,000 entries that is ~110,000 process starts in a row. Until the loop is done, everything not yet re-added is simply not blocked — and the entries at the end of the file come back last.</p>
<p dir="auto"><strong>Measured</strong> (ipset size sampled every 15 s during a normal update on a 10-core server):</p>
<pre><code>10:59:51  89,645 entries
11:00:21   3,474   &lt;- flushed
11:01:21  28,960
11:02:22  54,211
11:03:22  71,989
11:04:07  89,658   &lt;- complete again
</code></pre>
<p dir="auto">So roughly 4 minutes with on average half the list missing — on a quiet server. When the server is busy the loop takes longer than 10 minutes, the API call times out on the client side, and the hole lasts accordingly. With an hourly sync that is 7 % of the time or more with a partly open firewall.</p>
<p dir="auto"><strong>Two more problems in the same code path</strong></p>
<ol>
<li><strong>No serialization.</strong> <code>network.setBlocklist()</code> writes <code>blocklist.txt</code> and starts <code>setblocklist.sh</code> without any lock. If a second call arrives while the first script is still running (easy when the first one takes 10+ minutes, or when a client gives up and retries), the second script flushes the set in the middle of the first one's loop, while the first one keeps reading a file that has just been rewritten.</li>
<li><strong>Partial apply on error.</strong> The script runs with <code>set -e</code>. If one <code>ipset add</code> fails, the script stops and the rest of the list is never loaded until the next update — while the database says the full list is active. That matches what was described in #10568 ("Blocked IP addresses in Cloudron but still able to access WordPress sites": "the block list never completely applied").</li>
</ol>
<p dir="auto"><strong>Fix: load into a temporary set and swap atomically</strong></p>
<p dir="auto"><code>ipset</code> has exactly the tools for this: build the new list in a temporary set with a single <code>ipset restore</code> (one process instead of 110,000), then <code>ipset swap</code> it with the live set. The swap is atomic, the iptables rules keep pointing at the same set name, and the live set is never empty. A <code>flock</code> serializes concurrent calls. If any entry is rejected, <code>ipset restore</code> fails as a whole and the old list stays fully active instead of a partial one.</p>
<p dir="auto">Measured on the same server with the same <code>blocklist.txt</code> (114,330 entries, 24,670 of them IPv6), loaded into separate test sets: <strong><code>ipset restore</code> 2.2 seconds, <code>ipset swap</code> 3 milliseconds</strong> — instead of 4 to 10+ minutes with a partly empty firewall. The resulting set had exactly the same number of entries as the live one.</p>
<p dir="auto">Drop-in replacement for <code>src/scripts/setblocklist.sh</code> (same input file, same set names, same <code>--check</code>) :</p>
<pre><code class="language-bash">#!/bin/bash

set -eu -o pipefail

if [[ ${EUID} -ne 0 ]]; then
    echo "This script should be run as root." &gt; /dev/stderr
    exit 1
fi

if [[ $# == 1 &amp;&amp; "$1" == "--check" ]]; then
    echo "OK"
    exit 0
fi

[[ "${BOX_ENV}" == "test" ]] &amp;&amp; exit

exec 9&gt;/run/cloudron-setblocklist.lock
flock 9 # one blocklist update at a time

user_firewall_json="/home/yellowtent/platformdata/firewall/blocklist.txt"

has_ipv6=0
ipset list -n cloudron_blocklist6 &gt;/dev/null 2&gt;&amp;1 &amp;&amp; has_ipv6=1 # kernel may not have ipv6

# same type and maxelem as in cloudron-firewall.sh, otherwise swap refuses
ipset destroy cloudron_blocklist_new 2&gt;/dev/null || true
ipset create cloudron_blocklist_new hash:net maxelem 262144
if [[ ${has_ipv6} == 1 ]]; then
    ipset destroy cloudron_blocklist6_new 2&gt;/dev/null || true
    ipset create cloudron_blocklist6_new hash:net family inet6 maxelem 262144
fi

if [[ -f "${user_firewall_json}" ]]; then
    # without the -n block, any last line without a new line won't be read it!
    while read -r line || [[ -n "$line" ]]; do
        [[ -z "${line}" ]] &amp;&amp; continue # ignore empty lines
        [[ "$line" =~ ^#.*$ ]] &amp;&amp; continue # ignore lines starting with #
        if [[ "$line" == *":"* ]]; then
            if [[ ${has_ipv6} == 1 ]]; then echo "add cloudron_blocklist6_new ${line}"; fi
        else
            echo "add cloudron_blocklist_new ${line}"
        fi
    done &lt; "${user_firewall_json}" | ipset restore -exist # -exist ignores duplicates
fi

ipset swap cloudron_blocklist_new cloudron_blocklist
ipset destroy cloudron_blocklist_new
if [[ ${has_ipv6} == 1 ]]; then
    ipset swap cloudron_blocklist6_new cloudron_blocklist6
    ipset destroy cloudron_blocklist6_new
fi
</code></pre>
<p dir="auto"><strong>Background — this has come up for years</strong></p>
<ul>
<li><a href="https://forum.cloudron.io/topic/1838">#1838</a> (2020): firewall restart takes 15–20 s — "the time the kernel is taking to add your blocklist to ipset".</li>
<li><a href="https://forum.cloudron.io/topic/3154">#3154</a> (2020): country lists cause response timeouts; fixed by removing the API timeout (<code>req.clearTimeout()</code>), but the loading itself stayed line by line.</li>
<li><a href="https://forum.cloudron.io/topic/3795">#3795</a> (2020–2024): many users keep blocklists up to date with scripts against the API ("1.5 minutes per API call").</li>
<li><a href="https://forum.cloudron.io/topic/6010">#6010</a> (2021): server crash after adding FireHOL level1.</li>
<li><a href="https://forum.cloudron.io/topic/10547">#10547</a> / <a href="https://forum.cloudron.io/topic/10568">#10568</a> (2023): "hash is full", and blocked IPs still getting through because the list never completely applied.</li>
<li><a href="https://forum.cloudron.io/topic/15536">#15536</a> (2026): body size limit raised to 20 MB — thanks! — which makes large lists possible, and makes the slow, non-atomic apply more visible.</li>
<li><a href="https://forum.cloudron.io/topic/15702">#15702</a> (2026): robi on large IP sets; the FSF/reaction approach he quotes is exactly this: export/restore instead of rule by rule, "nearly instantaneous".</li>
</ul>
<p dir="auto"><strong>A possible next step (not needed for the fix)</strong></p>
<p dir="auto">With an atomic apply that takes seconds, frequent full updates become cheap. An API to add or remove single entries (without resending the whole list) would be a nice follow-up for tools that react to attacks in real time, but the fix above is the important part: it closes the hole on every update, removes the timeouts and makes concurrent updates safe, without any change to the API, database or UI.</p>
<p dir="auto">Happy to test a patched version on my servers.</p>
]]></description><link>https://forum.cloudron.io/topic/16188/bug-network-blocklist-is-flushed-and-refilled-line-by-line-the-firewall-is-partly-open-for-minutes-on-every-update-fix-included</link><generator>RSS for Node</generator><lastBuildDate>Thu, 05 Nov 2026 10:29:30 GMT</lastBuildDate><atom:link href="https://forum.cloudron.io/topic/16188.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 24 Oct 2026 10:07:09 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Bug: network blocklist is flushed and refilled line by line — the firewall is partly open for minutes on every update (fix included) on Mon, 26 Oct 2026 17:20:10 GMT]]></title><description><![CDATA[<p dir="auto">Thanks for the report. I have fixed this now.</p>
]]></description><link>https://forum.cloudron.io/post/131942</link><guid isPermaLink="true">https://forum.cloudron.io/post/131942</guid><dc:creator><![CDATA[girish]]></dc:creator><pubDate>Mon, 26 Oct 2026 17:20:10 GMT</pubDate></item></channel></rss>