How To

Is ZimaOS Safe? Harden Remote Access and Ports on ZimaBoard 2

By the time ZimaOS onboarding finishes, a ZimaBoard 2 is already a member of a ZeroTier network. Nothing in the setup flow asks about it. That network is set to public, the box ships with no firewall, and SSH, Samba and a web terminal all listen on every interface the board has, including the virtual one. None of that makes ZimaOS unsafe on its own. It does mean the defaults assume a trusted network and a Remote ID nobody else will ever see, and you should decide for yourself whether those assumptions hold.

Original content from computingforgeeks.com - post 172033

So, is ZimaOS safe? This guide answers that with evidence from a real board: every port ZimaOS opens, how ZimaOS remote access over ZeroTier actually works, the published ZimaOS vulnerabilities and which release fixed them, and the gaps (no firewall, no 2FA, password SSH, NTLMv1 on Samba). Then it closes them in seven steps. If the board is still in its box, start with our ZimaBoard 2 setup guide in this series, which covers first boot, the update and enabling SSH.

Is ZimaOS safe? The short answer

ZimaOS is reasonably safe on a home LAN once it is updated, and noticeably less safe than it looks if you leave the defaults alone. Our board shipped on ZimaOS 1.5.0, a release covered by a critical login bypass that IceWhale fixed in 1.5.3, so the first update is a security fix, not a nice-to-have. After updating, the unauthenticated API endpoints named in past advisories all refused us. The weak points that remain are configuration, not bugs, and every one of them has a fix below.

What we checkedZimaOS defaultRiskFix
Remote Access (ZeroTier)On, public network, board is its own controllerAnyone holding the Remote ID can joinStep 2 or step 3
FirewallNone: INPUT policy ACCEPT, no nftablesEvery service reachable on every interfaceStep 7
SSHRoot login off, per-source penalties on, passwords allowedPassword guessing, slowed but not stoppedStep 4
Web terminal (ttyd, port 7681)Runs with SSH, plain HTTPPassword crosses the network unencryptedStep 4 and step 7
Web UIHTTP only; HTTPS optional, no redirectSession traffic readable on the wireStep 5
SambaSMB2 minimum, NTLMv1 permitted, force user rootWeak auth accepted, files written as rootStep 6
Two-factor loginNot supportedOne password guards the dashboardKeep the UI off the internet
Known CVEsFixed in current releasesOld images are exposedStep 1

What a fresh ZimaOS install exposes

Start with what is listening. On the board (over SSH, as the admin user you created during setup) list every TCP socket that is not bound to loopback:

sudo ss -tlnp | grep -v 127.0.0.1

Everything here binds to 0.0.0.0 or *, which means the LAN, Docker’s bridge, the libvirt bridge and the ZeroTier interface alike:

State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
LISTEN 0      0            0.0.0.0:3702       0.0.0.0:*    users:(("wsdd2",pid=1826,fd=9))
LISTEN 0      0            0.0.0.0:3702       0.0.0.0:*    users:(("wsdd2",pid=1826,fd=8))
LISTEN 0      0            0.0.0.0:3702       0.0.0.0:*    users:(("wsdd2",pid=1826,fd=7))
LISTEN 0      0            0.0.0.0:7681       0.0.0.0:*    users:(("ttyd",pid=1212,fd=13))
LISTEN 0      0            0.0.0.0:139        0.0.0.0:*    users:(("smbd",pid=1731,fd=29))
LISTEN 0      0            0.0.0.0:22         0.0.0.0:*    users:(("sshd",pid=1207,fd=6))
LISTEN 0      0            0.0.0.0:445        0.0.0.0:*    users:(("smbd",pid=1731,fd=28))
LISTEN 0      0            0.0.0.0:37163      0.0.0.0:*    users:(("znet",pid=2671,fd=26))
LISTEN 0      0            0.0.0.0:5355       0.0.0.0:*    users:(("wsdd2",pid=1826,fd=17))
LISTEN 0      0            0.0.0.0:5355       0.0.0.0:*    users:(("wsdd2",pid=1826,fd=16))
LISTEN 0      0            0.0.0.0:5355       0.0.0.0:*    users:(("wsdd2",pid=1826,fd=15))
LISTEN 0      0            0.0.0.0:38597      0.0.0.0:*    users:(("znet",pid=2671,fd=30))
LISTEN 0      0            0.0.0.0:1910       0.0.0.0:*    users:(("ssdpd",pid=1614,fd=3))
LISTEN 0      0            0.0.0.0:9993       0.0.0.0:*    users:(("zerotier-one",pid=13236,fd=7))
LISTEN 0      0                  *:3702             *:*    users:(("wsdd2",pid=1826,fd=10))
LISTEN 0      0                  *:139              *:*    users:(("smbd",pid=1731,fd=27))
LISTEN 0      0                  *:45189            *:*    users:(("znet",pid=2671,fd=29))
LISTEN 0      0                  *:80               *:*    users:(("zimaos-gateway",pid=1189,fd=11))
LISTEN 0      0                  *:22               *:*    users:(("sshd",pid=1207,fd=7))
LISTEN 0      0                  *:443              *:*    users:(("zimaos-gateway",pid=1189,fd=21))
LISTEN 0      0                  *:445              *:*    users:(("smbd",pid=1731,fd=26))
LISTEN 0      0                  *:37573            *:*    users:(("znet",pid=2671,fd=31))
LISTEN 0      0                  *:5355             *:*    users:(("wsdd2",pid=1826,fd=18))
LISTEN 0      0                  *:9527             *:*    users:(("zimaos",pid=1617,fd=24))
LISTEN 0      0                  *:9993             *:*    users:(("zerotier-one",pid=13236,fd=8))   

Port 443 only appears because we had already switched on HTTPS (step 5). SSH on 22 and the web terminal on 7681 appear once SSH Access is enabled in Developer mode. Samba, the discovery daemons, the ZimaOS backend on 9527 and both remote access services (zerotier-one and IceWhale’s own znet) are there from the first boot. Nmbd also binds NetBIOS name service to the ZeroTier address specifically, so the board announces itself on the virtual network too.

Nothing filters any of it. Check the firewall on both address families:

sudo iptables -S INPUT; sudo ip6tables -S INPUT

The only rule is the jump libvirt adds for its own bridge. The policy is ACCEPT:

-P INPUT ACCEPT
-A INPUT -j LIBVIRT_INP
-P INPUT ACCEPT
-A INPUT -j LIBVIRT_INP

ZimaOS does not ship nft, ufw, firewalld or fail2ban. The iptables it does ship is the nf_tables backend (iptables v1.8.11 (nf_tables)), plus iptables-save and iptables-restore, which is enough to build a firewall in step 7. The root filesystem is a read-only squashfs image, so you cannot install a firewall package, but /etc is a writable overlay whose upper layer lives in /mnt/overlay/upper_etc.

How ZimaOS remote access works: a public ZeroTier network the board controls

The Remote Access feature in Settings, Network is ZeroTier, the same peer-to-peer VPN many homelabs already run. Each ZimaOS device runs its own ZeroTier network controller and hosts a private-to-it network called IceWhale-RemoteAccess. Your board is not sharing a network with other IceWhale customers. IceWhale’s remote access documentation says the same thing: ZimaOS runs its own network controller, so no outside party holds administrative rights over it.

What the documentation doesn’t spell out is how that network admits members. Ask the local ZeroTier service:

sudo zerotier-cli info
sudo zerotier-cli listnetworks

We replaced the real node address and network ID in this output with made-up values of the same shape. Notice that the first 10 hex digits of the network ID match the node address, which is how ZeroTier names a network hosted by that node’s own controller. The type column reads PUBLIC:

200 info 8d4f1c27a0 1.14.0 TUNNELED
200 listnetworks <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>
200 listnetworks 8d4f1c27a0e1b5c3 IceWhale-RemoteAccess 6a:1f:03:aa:9e:5b OK PUBLIC zt3j2g3ido fd8d:4f1c:27a0:e1b5:c399:938d:4f1c:27a0/88,10.147.13.1/24

The controller API on port 9993 confirms it. It needs the local auth token, so run these on the board:

TOKEN=$(sudo cat /var/lib/zerotier-one/authtoken.secret)
NWID=$(sudo zerotier-cli listnetworks | awk 'NR==2{print $3}')
curl -s -H "X-ZT1-Auth: ${TOKEN}" "http://127.0.0.1:9993/controller/network/${NWID}" | python3 -c "import json,sys;d=json.load(sys.stdin);print(json.dumps({k:d[k] for k in ['name','private','ipAssignmentPools','routes']},indent=2))"

"private": false is the line that matters. The pool hands out 10.147.13.1 to 10.147.13.254, and the only route is that /24:

{
  "name": "IceWhale-RemoteAccess",
  "private": false,
  "ipAssignmentPools": [
    {
      "ipRangeEnd": "10.147.13.254",
      "ipRangeStart": "10.147.13.1"
    }
  ],
  "routes": [
    {
      "target": "10.147.13.0/24",
      "via": null
    }
  ]
}

ZeroTier’s protocol documentation is blunt about what public means: access control is disabled, new members are automatically marked as authorized by the host controller, and it is not possible to de-authorize a member from a public network. The network’s flow rules on our board were the default accept rules, so nothing narrows what a member can reach. Put plainly, the 16-character Remote ID is the credential. Anyone who has it and runs a ZeroTier client can join, get an address in 10.147.13.0/24 and talk to the board’s SSH, Samba, web terminal, web UI and any Docker app ports it publishes at 10.147.13.1. The route covers only that /24, so a joined stranger reaches the board itself, not the rest of your LAN.

We did not join the network from a second machine to prove it, so treat this as the documented behavior of a public ZeroTier network rather than a demonstrated break-in. IceWhale’s own wording lines up: its docs tell you to treat the Remote ID like a password, and the Settings page warns that the ID is critical to the security of the device:

ZimaOS Settings Network page with Remote access on, the Remote ID redacted and the Reset Network ID menu open

Two other things happen while Remote Access is on. ZeroTier on our board ran in TUNNELED mode, relaying over TCP 443 because it couldn’t punch a direct UDP path. And znet, the IceWhale service that owns a second tunnel interface (tun0 at 10.126.126.1), kept connections open to public DNS-over-HTTPS resolvers on TCP 443 and to six relay servers on TCP 11010. That is how Zima Client finds the board from outside, and it is also outbound traffic you should know about if you watch your firewall logs.

1. Update ZimaOS before anything else

Our ZimaBoard 2 arrived on ZimaOS 1.5.0 in September 2026. That image is affected by the two critical advisories in the table below, one of which lets anyone log in to the web UI with a system username such as www-data and any password. Update from Settings, General, Update (the setup guide in this series walks through it, including the blank-screen gotcha after the reboot), and do it before the board goes anywhere near a port forward or a tunnel.

These are the advisories published on the ZimaOS GitHub security page, with the affected and patched versions each advisory lists:

AdvisoryIssueSeverityAffectedPatched
GHSA-gq27-h664-6xp4Authenticated arbitrary file read via /v2_1/files/file/download path traversalHigh1.6.11.7.1
CVE-2026-21891Login bypass with system usernames (root, www-data, nobody and others) and any passwordCritical1.5.0 and older1.5.3
CVE-2026-28798Unauthenticated access to internal services through /v1/sys/proxy when a Cloudflare Tunnel exposes the UICritical1.5.0 and older1.5.3
CVE-2025-64427SSRF through /v1/sys/proxy for authenticated usersHigh1.5.0 and older1.5.3
CVE-2026-28442Deleting system files through the files APIHigh1.5.2-beta31.5.3
CVE-2026-28286Creating files in system directories through the files APIHigh1.5.2-beta31.5.3
GHSA-57r9-43pc-gcp2Regression: /v1/users/name discloses usernames without authModerate1.5.01.5.1

The list goes further back: five 2024 advisories fixed in 1.2.5 (file read, directory listing, an info leak and two username disclosures), and two 2025 localhost file API bugs (CVE-2025-58431 and CVE-2025-58432) whose advisories list 1.4.1 and older as affected without naming a patched release. After updating, confirm the unauthenticated endpoints behave. Set the board’s address once, from any machine on the LAN:

export ZIMA_IP="192.168.1.177"
export ZIMA_USER="cfgadmin"
export LAN_CIDR="192.168.1.0/24"

Swap in your board’s IP, the admin username you picked during onboarding and your LAN subnet. If the board still takes its address from DHCP, pin it first with our ZimaOS static IP guide, because the commands below and your bookmarks are written against that address. The variables only live in the current shell, so re-export them if you open a new terminal. Now probe the endpoints with no session token:

for p in /v1/sys/debug /v1/sys/version /v1/users/name /v1/users/status; do printf '%s ' "$p"; curl -s -o /dev/null -w '%{http_code}\n' "http://${ZIMA_IP}${p}"; done

On ZimaOS v1.7.1 the username endpoint from the 1.5.0 regression now returns 401, along with the debug and version endpoints:

/v1/sys/debug 401
/v1/sys/version 401
/v1/users/name 401
/v1/users/status 200

The one 200 is /v1/users/status, which the login page needs before anyone signs in. It gives away only that the device is initialized and its GPU count: {"success":200,"message":"ok","data":{"gpuCount":0,"gpus":0,"initialized":true,"key":""}}. A 200 on /v1/users/name means you are on an unpatched image.

2. Turn off ZimaOS remote access if you don’t use Zima Client

If you never installed Zima Client on a phone or laptop, the ZeroTier network is doing nothing for you except existing. Open Settings, Network and switch Remote access off:

ZimaOS remote access toggle switched off in Settings Network with the Remote ID row gone

The Remote ID row disappears, but in our capture the Connection list above it still showed the Virtual Network (Remote) entry with 10.147.13.1. Don’t trust the UI for this. Check the services instead:

systemctl is-active zerotier-one znet

ZeroTier stops. znet keeps running:

inactive
active

On our board, ip -br link | grep -E 'zt|tun' printed nothing afterwards, so both the ZeroTier interface and tun0 were gone, and the outbound TCP sessions to the relays and DNS-over-HTTPS resolvers closed. znet itself stayed active with a handful of UDP sockets open. Turning the toggle back on brought back the same network ID, so switching off is not a way to rotate a leaked ID. That is what step 3 is for.

If you want remote access without a public network, IceWhale’s App Store lists Tailscale, WireGuard Easy, Firefly and NetBird. Tailscale is the least effort, and every device has to sign in to your own tailnet before it can connect. We cover it on a normal Linux host in the Tailscale install guide, and the WireGuard walkthrough explains the key exchange if you prefer running your own tunnel.

3. If you keep Remote Access, guard and audit the Remote ID

Keep Remote Access on only if you use Zima Client, and then treat the Remote ID the way you treat an SSH private key. Do not paste it into forum posts, support chats or screenshots (ours is redacted above for exactly this reason). Because a public ZeroTier network cannot de-authorize a member, the only way to shut out someone who has the ID is to replace it. The three-dot menu next to Copy ID has a Reset Network ID option. According to IceWhale’s docs the old ID stops working immediately, and existing connections and shares become invalid, so every Zima Client device has to reconnect afterwards.

You can also see who has joined. The controller keeps a member list, and on a network nobody else should be on it holds exactly one entry, the board itself. Reuse the TOKEN and NWID variables from earlier:

curl -s -H "X-ZT1-Auth: ${TOKEN}" "http://127.0.0.1:9993/controller/network/${NWID}/member"; echo

Our list had one member, the board’s own node address (sanitized here), followed by the member revision:

{"8d4f1c27a0":2}

Each phone or laptop running Zima Client adds one node address. Any address you can’t match to a device you own means the ID has leaked: reset it.

4. Lock down SSH and the web terminal

SSH Access and the web-based terminal live in Settings, General, Developer mode, alongside HTTPS and the Samba switches used in the next two steps:

ZimaOS developer mode settings with SSH Access, web-based terminal, HTTPS, DLNA and Samba toggles

The shipped sshd configuration is better than most appliances. Dump the effective settings:

sudo sshd -T | grep -Ei "^(permitrootlogin|passwordauthentication|maxauthtries|persourcepenalties|persourcemaxstartups|pubkeyauthentication|listenaddress) "

Root login is off, and OpenSSH’s per-source penalties throttle an address that keeps failing (20 seconds for each connection that ends in failed logins, accumulating up to 10 minutes). Password login is still on:

listenaddress [::]:22
listenaddress 0.0.0.0:22
maxauthtries 4
permitrootlogin no
pubkeyauthentication yes
passwordauthentication yes
persourcemaxstartups 3
persourcepenalties crash:90 authfail:20 noauth:10 grace-exceeded:45 refuseconnection:10 max:600 min:5 max-sources4:65536 max-sources6:65536 overflow:permissive overflow6:permissive

The simplest fix is to leave SSH Access off unless you are actively using it. The web terminal is the bigger problem. ttyd listens on port 7681 over plain HTTP, and although it runs a login prompt (/usr/libexec/ttyd-login checks the user, then hands over to login), your password travels unencrypted. It is tied to SSH in its unit file:

systemctl cat ttyd | grep -E "Requires|PartOf|ExecStart"

The unit requires sshd.service and is declared part of it, so stopping SSH stops the terminal with it:

Requires=sshd.service
PartOf=sshd.service
ExecStart=/usr/bin/ttyd -W /usr/libexec/ttyd-login

If you do keep SSH on, switch it to keys. The admin account’s home directory on ZimaOS is /DATA, not /home, and /DATA is owned by root with mode 755, so ssh-copy-id cannot create .ssh there on its own. Create it first, on the board and logged in as your admin user:

sudo install -d -m 700 -o "$(id -un)" -g "$(id -gn)" /DATA/.ssh

A root-owned, non-writable home is fine for sshd’s StrictModes check, which is on. Now copy your key from your workstation and prove it works:

ssh-copy-id "${ZIMA_USER}@${ZIMA_IP}"
ssh -o PasswordAuthentication=no "${ZIMA_USER}@${ZIMA_IP}" 'echo key login works'

Do not skip that second command. Only when it prints key login works should you turn passwords off. ZimaOS’s sshd_config has no Include line and no sshd_config.d directory, so edit the explicit PasswordAuthentication yes line in place, validate, and restart. The stock file already sets KbdInteractiveAuthentication no, so PAM’s keyboard-interactive route is closed and this one line is enough. Keep your current session open while you do it:

sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl restart sshd
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication) '

Both lines should now read no. We confirmed the stock values and the effective result with sshd -T overrides on our board, but did not switch the review unit itself to key-only login, so run the key test before you rely on it. The edit lands in the /etc overlay, so check it again after each ZimaOS update and after toggling SSH Access in the UI. This does nothing for ttyd, which authenticates through login and PAM, not sshd, and still accepts passwords. That is one more reason to firewall port 7681 in step 7.

5. Turn on HTTPS for the web UI (and know what it doesn’t do)

The HTTPS toggle sits in the same Developer mode screen. Flip it and ZimaOS asks you to save and reload the WebUI:

ZimaOS WebUI HTTPS dialog asking to save and refresh after enabling HTTPS in developer mode

After Save, the gateway config on the board has TLS enabled on 443:

grep -A3 '\[ssl\]' /etc/casaos/gateway.ini

The certificate path points at a bundle ZimaOS generated itself:

[ssl]
enabled   = true
port      = 443
cert_path = /var/lib/casaos/ssl/zimaos.local.fullchain.pem

ZimaOS itself has no openssl binary, so inspect the certificate from your workstation (the -ext option needs OpenSSL 1.1.1 or newer; on macOS use Homebrew’s openssl rather than the bundled LibreSSL):

echo | openssl s_client -connect "${ZIMA_IP}:443" -servername zimaos.local 2>/dev/null | openssl x509 -noout -subject -issuer -enddate -ext subjectAltName

It is signed by a ZimaOS CA and valid only for zimaos.local, so browsers will warn when you connect by IP:

subject=C=CN, O=IceWhaleTech, OU=ZimaOS, CN=ZimaOS Local
issuer=C=CN, ST=Shanghai, L=Shanghai, O=IceWhaleTech, OU=ZimaOS, CN=ZimaOS-CA
notAfter=Mar 25 09:09:42 2029 GMT
X509v3 Subject Alternative Name:
    DNS:zimaos.local

The catch: HTTP is not redirected. Port 80 keeps serving the full UI after HTTPS is on:

curl -sI "http://${ZIMA_IP}/" | head -1

A 200, not a 301:

HTTP/1.1 200 OK

Bookmark https://zimaos.local/ (or the HTTPS IP address) and use only that. Anyone who types the bare IP still gets plaintext, and HTTPS does nothing to stop someone on the network from reaching the login page. Never port-forward the UI to the internet. With no 2FA, one password is all that stands in front of it.

6. Tighten Samba where ZimaOS allows it

Samba is on from the first boot. The Supports Samba1 switch is off by default, which is the right call, and it should stay off. Look at the global settings ZimaOS generates:

sudo testparm -s 2>/dev/null | grep -Ei "min protocol|ntlm auth|map to guest|force user|create mask|\["

Two lines stand out. ntlmv1-permitted accepts the old NTLMv1 challenge, which is crackable offline, and force user = root means every file written over SMB is owned by root on disk:

[global]
	map to guest = Bad User
	ntlm auth = ntlmv1-permitted
	server min protocol = SMB2
	force user = root

Guest access is limited in practice: an anonymous session listed only IPC$, no shares. Resist the urge to edit smb.conf, though. ZimaOS regenerates it (the file’s timestamp changed when we signed in), so hand edits get overwritten. That is the opposite of a hand-built Samba server, where the config file is yours. What you can control here is exposure. Turn the Samba service switch off on boards that don’t serve files over SMB, give share users long unique passwords, and restrict ports 139 and 445 to your LAN with the firewall in the next step.

7. Add a LAN-only firewall to ZimaOS

ZimaOS has no firewall UI, but it ships iptables and systemd, and /etc is writable. The ruleset below adds one chain in front of INPUT. Traffic from loopback, from established connections and from your LAN (matched by both the physical interface and the subnet) passes untouched. SSH, the web terminal and Samba arriving on any other interface, the ZeroTier and znet tunnels included, get dropped. Matching the interface matters: ZeroTier emulates Ethernet, so a remote member can reach the board over a link-local IPv6 address, and a rule keyed on the address alone would let it straight in. The web UI on 80 and 443 and the ZimaOS backend on 9527 are left alone, because Zima Client needs them. That means anyone on the ZeroTier network can still reach the password-only login page, so if you don’t use Zima Client, switch Remote Access off as in step 2 rather than relying on this unit. Create the IPv4 rules file:

sudo vim /etc/zimaos-lan-only.rules

Paste the ruleset exactly as written. The eth0 and 192.168.1.0/24 values get replaced with yours in a moment:

*filter
:ZIMA-LAN-ONLY - [0:0]
-A ZIMA-LAN-ONLY -i lo -j RETURN
-A ZIMA-LAN-ONLY -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
-A ZIMA-LAN-ONLY -i eth0 -s 192.168.1.0/24 -j RETURN
-A ZIMA-LAN-ONLY -p tcp -m multiport --dports 22,139,445,7681 -j DROP
-A ZIMA-LAN-ONLY -p udp -m multiport --dports 137,138 -j DROP
COMMIT

SSH, Samba and ttyd all listen on IPv6 as well, and the ZeroTier interface carries an IPv6 address, so the IPv6 file matters just as much. Open it:

sudo vim /etc/zimaos-lan-only6.rules

This version accepts any IPv6 source that arrives on the LAN interface (link-local, ULA or global), so Windows clients that reach Samba over IPv6 keep working:

*filter
:ZIMA-LAN-ONLY - [0:0]
-A ZIMA-LAN-ONLY -i lo -j RETURN
-A ZIMA-LAN-ONLY -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
-A ZIMA-LAN-ONLY -i eth0 -j RETURN
-A ZIMA-LAN-ONLY -p tcp -m multiport --dports 22,139,445,7681 -j DROP
COMMIT

Swap in your LAN interface (the one holding your LAN address in ip -br addr; eth0 on ours) and subnet, then dry-run both files. If you cable both 2.5GbE ports into the LAN, duplicate the RETURN line for the second interface. Export LAN_CIDR on the board too, and LAN_IF alongside it. --test parses the ruleset against the running kernel without touching the live tables, and prints nothing when the rules are valid:

export LAN_IF="eth0"
sudo sed -i "s|192.168.1.0/24|${LAN_CIDR}|; s|-i eth0|-i ${LAN_IF}|" /etc/zimaos-lan-only.rules
sudo sed -i "s|-i eth0|-i ${LAN_IF}|" /etc/zimaos-lan-only6.rules
sudo iptables-restore --test --noflush /etc/zimaos-lan-only.rules && echo v4 ok
sudo ip6tables-restore --test --noflush /etc/zimaos-lan-only6.rules && echo v6 ok

Rules loaded by hand vanish at reboot, so a oneshot unit loads them at boot. Create it:

sudo vim /etc/systemd/system/zimaos-lan-only.service

The ExecStartPre deletes keep a restart from stacking duplicate jumps in INPUT:

[Unit]
Description=Restrict SSH, web terminal and Samba to the LAN
Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/sbin/iptables -D INPUT -j ZIMA-LAN-ONLY
ExecStartPre=-/usr/sbin/ip6tables -D INPUT -j ZIMA-LAN-ONLY
ExecStart=/usr/sbin/iptables-restore --noflush /etc/zimaos-lan-only.rules
ExecStart=/usr/sbin/iptables -I INPUT 1 -j ZIMA-LAN-ONLY
ExecStart=/usr/sbin/ip6tables-restore --noflush /etc/zimaos-lan-only6.rules
ExecStart=/usr/sbin/ip6tables -I INPUT 1 -j ZIMA-LAN-ONLY
ExecStop=-/usr/sbin/iptables -D INPUT -j ZIMA-LAN-ONLY
ExecStop=-/usr/sbin/ip6tables -D INPUT -j ZIMA-LAN-ONLY

[Install]
WantedBy=multi-user.target

Validate the unit, then enable it. Run this from a LAN machine so your own SSH session matches the RETURN rule:

sudo systemd-analyze verify /etc/systemd/system/zimaos-lan-only.service
sudo systemctl daemon-reload
sudo systemctl enable --now zimaos-lan-only.service
sudo iptables -L ZIMA-LAN-ONLY -n -v --line-numbers

systemd-analyze verify also prints warnings about some stock ZimaOS units (legacy /var/run PID paths in smb.service, nmb.service and a few IceWhale services). Those aren’t yours and are harmless. Both rule files passed --test and the unit passed verify on our board. We did not leave the firewall running on the review unit, so reboot yours once and rerun the last command to confirm the chain comes back. The packet counters on the DROP lines are where blocked off-LAN SSH or SMB attempts should show up.

Keep your current SSH session open while you do this and log in from a second terminal before closing it. The ESTABLISHED rule keeps the first session alive, so if LAN_IF was wrong (the NIC sits in a bridge, or the cable is in the other 2.5GbE port) you can still back out with sudo systemctl disable --now zimaos-lan-only.service. Without that session, the fix needs a keyboard and a Mini DP screen on the board.

Two limits to be clear about. Ports published by Docker apps are DNATed through the FORWARD chain, so this INPUT chain doesn’t cover app ports; filter those in DOCKER-USER if you need to. If you rely on Zima Client for file access, test it after enabling the unit, since the rules deliberately block SMB over the virtual network. And if you took the Tailscale route from step 2 and want SSH or SMB over your tailnet, add -A ZIMA-LAN-ONLY -i tailscale0 -j RETURN below the eth0 line in both files and re-run the --test commands.

Does ZimaOS support 2FA?

No. ZimaOS has no two-factor authentication for the web UI or for local accounts. An IceWhale staff member confirmed it on the IceWhale community forum in December 2025 (“ZimaOS does not currently support 2FA”), and the thread is still collecting requests for it. Until it ships, the failure mode is simple: whoever gets the admin password gets the whole box, including Docker and root-owned SMB shares. Use a long unique password, keep the dashboard reachable only from your LAN or through a VPN you control, and put 2FA on that VPN instead (Tailscale’s login provider or hardware security keys both do the job).

ZimaOS security audit checklist

Run through this after setup and again after every ZimaOS update, because updates and UI toggles can undo edits in /etc:

  1. Settings, General, Update shows no pending release, and curl to /v1/users/name without a token returns 401.
  2. Remote access is off, or you use Zima Client and the controller’s member list holds only devices you own.
  3. The Remote ID has never appeared in a screenshot, ticket or chat. If it has, reset it.
  4. SSH Access is off when you aren’t using it. When it is on, sshd -T shows passwordauthentication no.
  5. You open the dashboard only over HTTPS, and it is not port-forwarded or tunnelled to the internet.
  6. Supports Samba1 is off, and Samba is off entirely on boards that don’t share files.
  7. systemctl is-active zimaos-lan-only prints active after a reboot, and iptables -S INPUT and ip6tables -S INPUT both include the ZIMA-LAN-ONLY jump.
  8. From a device that reaches the board over Zima Client’s ZeroTier network instead of the LAN, SSH and port 445 both time out.

A board that passes all eight is in far better shape than it was when it left the factory. The remaining risk is the one ZimaOS can’t fix for you yet: a single password on the dashboard.

Keep reading

UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Best Phone for GrapheneOS in 2026 (and Why to Skip the Pixel 11) Security Best Phone for GrapheneOS in 2026 (and Why to Skip the Pixel 11) Best PC for Blue Iris: Quick Sync, AI GPU and Drives by Camera Count Security Best PC for Blue Iris: Quick Sync, AI GPU and Drives by Camera Count Install SELinux Man Pages on Rocky Linux 10 AlmaLinux Install SELinux Man Pages on Rocky Linux 10

Leave a Comment

Press ESC to close