[Module] ZFW v1.0.10 — host firewall as a ZimaOS dashboard tile

I tracked this down. That “Open” button is part of ZimaOS’s Network widget, not ZFW. Each click spawns a fresh ttyd web-terminal on a random port from the kernel’s ephemeral range (32768–60999), bound to all interfaces. It’s a host process, so ZFW’s default-deny drops it — which is the firewall doing its job.

No, ZFW won’t auto-allow these, by design. The port is unpredictable, so “allowing ttyd” would mean opening the entire ephemeral range LAN-wide — and the same mechanism also serves a root login shell, so silently punching a hole there is exactly what a firewall shouldn’t do.

If you need it remotely with ZFW on, the cleanest option is one rule allowing TCP 32768–60999 from your admin machine’s IP only (ZFW supports port ranges + source-IP scoping). That keeps default-deny for the rest of the LAN. Otherwise, use those tools from the host locally.

The real fix belongs upstream: ZimaOS should route this terminal through its gateway on port 80 (already open), like every other backend, instead of binding a random public port.

Hi,

I have a question about the workflow.

When I create or modify a firewall rule, I click Apply (or Safe-Apply), which I would expect to apply the new rules immediately.

However, I often find that I have to disable the firewall completely and then enable it again before the new rule actually starts working.

Is this expected behavior, or should clicking Apply always be enough to activate new or modified rules without having to disable and re-enable the firewall?

Thanks!

Clicking Apply / Safe-Apply is enough — you shouldn’t need to disable and re-enable. A new or changed rule takes effect on all new connections immediately (I verified this on a live host: a fresh connection to a port is blocked the instant you Apply).

What you ran into is normal stateful-firewall behaviour: ZFW, like any conntrack-based firewall, lets already-open connections keep flowing. A new block rule only stops new connections — it can’t tear down a session that was already established. So if you had that service open in a browser tab or an app when you applied the rule, that one existing connection kept working until it closed. Disabling/re-enabling dropped the connection, so it reconnected and hit the block — which is why the toggle seemed to be required.
Two tips:

  1. Click Save rules first, then Safe-Apply on the Firewall tab — rule edits aren’t compiled until they’re saved.
  2. New connections are blocked instantly already. To make a block also drop an already-open connection without the toggle, ZFW 1.0.21 will flush the connection-tracking entries for a port the moment you block it — so the disable/enable step goes away entirely.
1 Like

I reinstalled ZFW v1.0.21 on ZimaOS v1.7.0 update.
Everything worked well locally. My websites and CMS.
Today, I connected to my CMS outdoors and they could not be reached. I disabled ZWF and now everything works externally.
Is this malfunction caused by ZimaOS v1.7.0 update?

I don’t think it’s a general issue. It should work.

First, make sure you’ve allowed the required port and confirmed the rule. If it still doesn’t work, try disabling ZFW and then enabling it again. That usually reloads the firewall rules, and external access should start working again.

I’ve been using ZFW since v1.0.10. Everything has always worked well.
By switching to ZimaOS v1.7.0 and reinstalling ZFW v1.0.21, everything works fine locally. My 443 CMS on a domain name seemed to work normally without any apparent problems.
All port rules for my apps are “allowed”. I checked them one by one. Everything works and opens normally.
It was on this day, when I connected outside my home, that I noticed that I could not reach my CMS. my wife also told me that she had not been able to synchronize external calendars on these websites.
I was able to join my ZimaOS with the ZeroTier application without any problem. I was also able to deactivate ZFW remotely and join my CMS.
There is an unresolved topic regarding network-dependent applications since the ZimaOS 1.7 update. Is this the cause?

I’m not sure if it’s related to the unresolved network issue in ZimaOS v1.7.0, so I can’t say for certain.

However, since everything started working again as soon as you disabled ZFW, I’d first try re-enabling ZFW and checking that all the required ports are still set to Allowed. Sometimes simply restarting the firewall causes it to reload the rules correctly.

If the problem comes back after enabling ZFW again, then it could be a bug introduced with the ZimaOS v1.7.0/ZFW v1.0.21 combination. That would definitely be worth reporting to the IceWhale team.

Short answer: this is not the ZimaOS 1.7 network-apps issue. It is a bug in ZFW, and I have a fix.

What is happening

Your CMS is a Docker container, and ZFW filters container ports in Docker’s
DOCKER-USER chain. That works — over IPv4. From your LAN you reach the
CMS over IPv4, so everything looks correct, and it is.

From outside, your domain name is very likely resolving to an AAAA record,
so the client connects over IPv6 — and that takes a completely different
path through the kernel. On a stock ZimaOS, Docker’s IPv6 support is off, so
containers have no IPv6 address of their own. An IPv6 connection to a published
port is instead handled by Docker’s docker-proxy helper on the host, which
means it is filtered by the host’s IPv6 input chain (ZFW-IN6) and never
reaches DOCKER-USER.

ZFW was only putting the host half of your rules into that IPv6 chain. No
Docker container port ever got an allow rule there, so with the default policy
on Deny, every IPv6 connection to your CMS was dropped — while the rule table
showed a green “Allow”, and while IPv4 kept working perfectly from the LAN.

There is a second half to this, and it explains why it only started now. Before
ZFW 1.0.17, the IPv6 chain was being written into the wrong iptables backend
(legacy while ZimaOS 1.6.2+ actually runs nf_tables), so it was inert — it
existed but no packet ever traversed it. You came from 1.0.10. Upgrading to
1.0.21 is the moment IPv6 filtering became real on your machine for the first
time. Nothing about your setup changed; ZFW’s IPv6 filtering simply started
working, and it was working wrongly.

ZeroTier kept working because ZFW always bypasses the ZeroTier and Tailscale
interfaces, and disabling ZFW removed the chain entirely — both consistent with
this.

Please confirm it — one command

Try to reach the CMS from outside, then on the ZimaOS box run:

sudo journalctl -k --since "-5 min" | grep -E "ZFW-IN6-DROP|ZFW-DOCK-DROP"
  • Lines with ZFW-IN6-DROP and an SRC= address containing colons
    → it is the IPv6 problem described above.
  • Lines with ZFW-DOCK-DROP and a normal public IPv4 SRC=
    → it is the second cause below, which is worth checking either way.

Please also check the Source column of your rules

Open the Rules tab and look at the Source column, not just the Action
column. If your rules were re-created on the fresh install (the “Recommended
defaults” set), every one of them has your LAN range as its source, e.g.
192.168.1.0/24 — including the rules for your Docker apps. Those rules read
“Allow”, and they allow only your LAN. Anyone coming from the internet has
a different source address, matches no rule, and is blocked. That alone would
produce exactly the symptom you describe, on IPv4, with no IPv6 involved.

Two rules with an IPv4 source can never apply to IPv6 either — that is a
limitation of ip6tables, not something I can work around: there is no way to
write “192.168.1.0/24” as an IPv6 match.

Fix right now, without waiting for a release

For each service that must be reachable from the internet, add one rule:

  • Action: Allow
  • Source: Any
  • Ports: the published port (443 for your CMS)
  • Zone: Docker

Keep your existing LAN rule next to it; leave the default policy on Deny. Then
add a second rule with Zone Host and Source Any for the same port — that
is the one that opens the IPv6 input chain on 1.0.21. Use Safe-Apply so the
120-second timer can rescue you, and test from a phone on mobile data (not from
your LAN, and not over ZeroTier — neither of those goes through the path that is
broken).

For services that should stay private, leave them scoped to your LAN. In that
case the IPv6 drop is doing what you want.

What changes in ZFW 1.0.22

  1. Rules now cover Docker-published ports on IPv6 as well, so the second rule
    above stops being necessary.
  2. A rule that cannot apply to IPv6 — because its source is an IPv4 address or
    range, or a country — is now marked IPv4 only in the rule table, with a
    tooltip explaining what to change. This used to be completely silent, which
    is the real reason this cost you a day.
  3. If the default policy is Deny, your host has a public IPv6 address, and not
    one rule reaches the IPv6 chain, the Rules tab now says so in a banner
    instead of showing a table full of green “Allow” rows.

One thing 1.0.22 deliberately does not do: it does not widen a LAN-scoped
rule to the internet on your behalf. That has to stay your decision — it just
stops being invisible.

The symptom description (“works locally, dead from outside, fine over ZeroTier”) is what pinned it down :slight_smile:

@Lintux

I think I understood the principle for opening port 443.
I would update ZWF to v1.0.22.
I will open port 443 for everyone in the Docker zone. Should I link each container individually or not?
I will test tonight with a cell phone and let you know tomorrow.
Thank you again for your responsiveness.

Yes to the update — and it’s directly about your case.

v1.0.22 came out of exactly this scenario: a service published on 443 in a container, reachable from the LAN, dead from the internet, while every rule in the table read “Allow”. So update first, then judge what’s still missing.

The cause is worth knowing, because it explains why your rules looked right. ZimaOS runs Docker without ip6tables (there’s no “ipv6” key in /etc/docker/daemon.json), so containers hold no IPv6 address and Docker publishes no IPv6 DNAT rule. Every published port is served by the userland docker-proxy listening on [::], which means an inbound IPv6 connection terminates on the host and is filtered in ZFW-IN6 — it never reaches FORWARD/DOCKER-USER. Before v1.0.22 a Docker-zone rule wrote no allow into ZFW-IN6, so IPv6 clients hit the catch-all drop while IPv4 stayed open through DOCKER-USER. That’s precisely why testing from inside the LAN couldn’t see it.

On linking each container: no, don’t — not for this.

Container binding does something different from what the name suggests. When a rule is bound to a container, the daemon looks that container up in the live inventory at recompile time and substitutes its currently published ports into the rule, replacing whatever port list you typed. It exists so a rule can follow an app across restarts and port remaps.

So if you bind a container in order to “open 443”, you don’t get 443 — you get every port that container publishes. That’s wider than you asked for, and it changes silently whenever the container’s port mapping changes.

For your goal, one plain port rule is both simpler and tighter:

  • one rule, ports ), action = allow.Matching in DOCKER-USER is by destination port (–ctorigdstport
    443), with no contthat single rulecovers whichever container publishes 443, and keeps working if you
    replace or rename
  • In practice only one container can publish 0.0.0.0:443 anyway.
    If several serviceon 443, only theproxy needs the rule at all — the backends behind it don’t need
    published ports.

Use container bindinely move and youwant the rule to track them. Not when you want one specific port
open.

One thing that wiln’t use “all ports” on a Docker-zone rule.

An explicit port (443) is mirrored into the IPv6 chain. ports =
all on a Docker-zo “every containerport” is not “every host port”, and mirroring it would switch off
the IPv6 default-dmaOS UI itself. Ifyou do it anyway, the Rules tab badges that rule “IPv4 only” with
the reason docker-s so. Give it theexplicit port.

Zone: prefer auto over docker. auto resolves per port against the
live published-por-IN,Docker-published ports to DOCKER-USER. If you ever move the
service to networklows it; a dockerrule would quietly stop covering it.

“For everyone” — be deliberate about the source. Source any means
anyone who can reale internet once443 is forwarded on the router. If the service is only meant for
LAN and VPN clientranges instead; ifit really is public, consider the per-rule rate limit.

Then Apply and Commit. Apply is a Safe-Apply with a 120-second
dead-man switch: it window, ZFWremoves the firewall entirely rather than restoring your previous
rules — you won’t left unprotectedwithout noticing.

Finally, verify from outside, over both families — this failure mode is invisible

curl -4 -sS -o /detps:///
curl -6 -sS -o /dev/null -w ‘%{http_code}\n’ https:///

If v4 answers and v6 hangs, that’s the exact gap v1.0.22 closes — and the Rules tab ell you per rulewhether it reaches the IPv6 chains and why not

To put it simply, I’m not a specialist, I open port 443 for everyone in auto mode.
Yes, only the Proxy (NPM) manages connections on the 443.
The Proxy (NPM) is set for 192.168.1.0/24 (CIDR) so I put it for everyone (ANY) ?
I installed the new version of ZFW on my test server to see what it looks like.
I’ll test this evening on my server in production and say tomorrow.

Short answer: only switch to Any if 443 has to be reachable from outside your LAN. Two separate things are going on here.

▎ 1. Your “443 for everyone, zone Auto” rule already covers NPM.
▎ Rules are evaluated top to bottom, first match wins — and an Allow rule never blocks anything. Traffic that doesn’t match a rule just falls through to the next one. So the NPM rule scoped to 192.168.1.0/24 is not restricting anything as long as the “443 from Any” rule exists. Leave it or delete it; for port 443 it changes nothing either way.

▎ 2. The scope only matters if a rule is the only one covering that port — and then it matters twice.
▎ - 192.168.1.0/24 matches only clients whose address is inside that range. A visitor from the internet arrives with a public source IP and is dropped.
▎ - More important since 1.0.22: an IPv4 CIDR is never applied to the IPv6 chain. ZFW mirrors a rule to IPv6 only when the source is Any or an IPv6 range. If your ZimaOS has a public IPv6 address and the default policy is Deny, a rule scoped to 192.168.1.0/24 means every client that resolves your domain to an AAAA record gets dropped — while the same site works fine from the LAN over IPv4.

▎ That’s exactly what the new “IPv4 only” badge next to a rule tells you in 1.0.22. Hover it and it explains why. And if no rule at all reaches IPv6, you get an orange banner above the rule table saying so. IPv6 drops are logged with the prefix ZFW-IN6-DROP and show up in the Events tab.

▎ Rule of thumb for NPM:
▎ - NPM reachable from the internet → source Any. That is the only setting that also covers IPv6.
▎ - NPM only used inside your house → keep 192.168.1.0/24, that’s the safer choice. Just don’t expect it to answer over IPv6.

I have just installed ZFW version 1.0.22 on my server in production.
I just opened port 82 for the ZimaOS WebUI for my local network.
I opened port 443 for everyone in auto (NPM).
I took the 2mn test and my websites are well distributed on the internet (test on my phone in external 5G).