A production-oriented bootstrap script for Ubuntu servers that performs a clean (from-scratch) firewalld reset, applies a default-deny inbound firewall posture, hardens Docker-published ports via the DOCKER-USER chain, and installs/enables Cockpit on port 9090.
This repository is designed for operators who care about security, determinism, and repeatability—and who prefer to avoid “mystery rules” and snowflake servers.
- Stops
firewalld(if running) - Purges
firewalldand removes legacy configuration directories:/etc/firewalld/var/lib/firewalld
- Reinstalls
firewalld, enables and starts it - Sets default zone to
public - Binds your primary interface to the
publiczone - Sets
publiczone target to DROP (default deny)
By default, the script only allows inbound TCP traffic to:
80/tcp443/tcp9090/tcp(Cockpit)
Everything else inbound is dropped.
- By default, inbound
22/tcpis blocked - Optionally, you can allow SSH from a specific trusted CIDR/IP using:
--allow-ssh-from 203.0.113.10/32
This uses firewalld rich rules to allow SSH only from the provided source range.
Critical warning: running this script remotely without SSH whitelisting will terminate your SSH access. Ensure console/KVM/IPMI access.
Installs and enables Cockpit services (socket activation):
- Installs packages:
cockpitcockpit-bridgecockpit-networkmanagercockpit-packagekitcockpit-storagedcockpit-wscockpit-system
- Enables and starts:
cockpit.socket
Access:
https://<server-ip>:9090
Cockpit often uses a self-signed certificate by default; browser warnings are expected unless you replace it.
If Docker is detected, the script programs the DOCKER-USER chain as an inbound allowlist for Docker-published ports.
Rules applied (conceptually):
RELATED,ESTABLISHED-> ACCEPT- Allow inbound TCP ports you specify (default:
80,443,9090) -> ACCEPT - Optional SSH allow from
--allow-ssh-from-> ACCEPT (port 22) - Everything else -> DROP
This protects you from the common “Docker published a port, now it’s reachable from the internet” surprise.
Instead of freezing the entire iptables ruleset with iptables-save / iptables-persistent (which can create long-term conflicts with firewalld and Docker’s dynamic rule management), the script persists only what we need:
- Creates:
/usr/local/sbin/docker-user-firewall.sh/etc/systemd/system/docker-user-firewall.service
- Enables and starts the service:
docker-user-firewall.service
On every boot (and after Docker starts), systemd re-applies the deterministic DOCKER-USER allowlist rules.
This approach is:
- More maintainable
- Less invasive
- More compatible with firewalld + Docker realities
- Ubuntu (APT-based)
- Root privileges (
sudo) - Internet access (to install packages)
- Docker is optional:
- If Docker is not installed, the DOCKER-USER hardening step is skipped.
docker-firewall-cockpit-bootstrap.sh- The main bootstrap script (clean firewalld install, allowlist, Cockpit, Docker hardening, persistence)
At runtime, it generates:
/usr/local/sbin/docker-user-firewall.sh/etc/systemd/system/docker-user-firewall.service
-
Copy the script to your server (or clone the repo).
-
Make it executable:
chmod +x docker-firewall-cockpit-bootstrap.sh- Run it:
Allow SSH from your current public IP:
sudo ./docker-firewall-cockpit-bootstrap.sh --allow-ssh-from <YOUR_PUBLIC_IP>/32If you have console/KVM access and deliberately want SSH blocked:
sudo ./docker-firewall-cockpit-bootstrap.shManually specify the public interface to attach to the public zone.
Example:
sudo ./docker-firewall-cockpit-bootstrap.sh --iface ens192When to use:
- VMware / vSphere (often
ens192) - Multi-NIC servers
- Complex routing where auto-detection is not reliable
Comma-separated list of inbound TCP ports to allow.
Example:
sudo ./docker-firewall-cockpit-bootstrap.sh --ports 80,443,9090Notes:
- This affects both:
- firewalld
publiczone open ports - Docker
DOCKER-USERallowlist
- firewalld
Allow inbound SSH only from a trusted source.
Example:
sudo ./docker-firewall-cockpit-bootstrap.sh --allow-ssh-from 203.0.113.10/32sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-allExpected highlights:
publicis activetarget: DROPports: 80/tcp 443/tcp 9090/tcp(or your custom list)
sudo systemctl status cockpit.socketAccess:
https://<server-ip>:9090
sudo iptables -L DOCKER-USER -n -vsudo systemctl status docker-user-firewall.service
sudo systemctl cat docker-user-firewall.service- Default deny inbound is the right baseline for servers.
- SSH should not be globally exposed in most modern deployments:
- Prefer VPN/Zero Trust access
- Or strictly whitelist source IPs (
--allow-ssh-from) - Use MFA / hardware keys where possible
- Cockpit exposure:
- Opening 9090 to the internet is usually not a great idea.
- Recommended patterns:
- Restrict 9090 with firewalld rich rules to your office/VPN CIDR
- Or place it behind a reverse proxy + authentication (SSO/MFA)
- Docker inbound exposure:
- DOCKER-USER allowlisting prevents accidental exposure of newly published ports.
- If you intentionally need a new port, add it via
--ports.
That’s expected if you ran the script remotely without --allow-ssh-from.
Fix:
- Access the server via console/KVM/IPMI
- Re-run with your trusted IP:
sudo ./docker-firewall-cockpit-bootstrap.sh --allow-ssh-from <YOUR_PUBLIC_IP>/32- Ensure
9090/tcpis in your allowed port list - Confirm firewalld is active and targeting the correct interface:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-allThis is by design: only allowlisted ports are reachable. Add the required port:
sudo ./docker-firewall-cockpit-bootstrap.sh --ports 80,443,9090,<NEW_PORT>Pin it:
sudo ./docker-firewall-cockpit-bootstrap.sh --iface <correct-interface>If you want to take this further:
- Restrict Cockpit access to office/VPN CIDR only (recommended)
- Add rate-limited logging for dropped inbound connections (SIEM-friendly)
- Add IPv6-first policy alignment (ip6tables/nftables)
- Integrate with CI/CD pipelines for immutable server bootstrapping
This project is licensed under the MIT License.
Please use the Issue > New Issue button to submit issues, feature requests or support issues directly to me. You can also send an e-mail to akin.bicer@outlook.com.tr.