---
title: "Stripping firewalld down to what you serve"
date: "2018-08-30"
path: "/189-stripping-firewalld-down-to-what-you-serve.html"
section: "/ramblings"
description: "Cutting a CentOS 7 firewalld public zone back to web traffic only and moving SSH, MySQL and the Virtualmin ports into a zone that only trusted addresses reach."
---

After the Virtualmin installer has run on a CentOS 7 box, the firewalld public zone is wide open. The installer assumes you'll run the whole stack, so mail, IMAP, POP3, FTP, DNS, MySQL, SSH, Webmin and Usermin are all reachable from anywhere. On a web server that only serves websites and sends mail through a relay, almost none of that should face the internet.

The approach: leave HTTP and HTTPS in the `public` zone, and move everything administrative into the `internal` zone, which only a short list of source addresses ever land in.

## How zones decide

firewalld picks a zone for each incoming packet. If the source address matches a source bound to a zone, that zone wins. Otherwise the zone of the interface the packet arrived on is used, which on a single-NIC server is `public`. So adding your office address as a source of `internal` means traffic from the office gets internal's rules, and everyone else gets public's.

See where you are first:

```bash
firewall-cmd --get-active-zones
firewall-cmd --zone=public --list-all
firewall-cmd --zone=internal --list-all
```

## Set up the trusted zone first

Do this before removing anything from public, or you can cut off your own SSH session.

```bash
firewall-cmd --zone=internal --add-source=203.0.113.10/32 --permanent
firewall-cmd --zone=internal --add-source=203.0.113.11/32 --permanent
firewall-cmd --zone=internal --add-service=ssh --permanent
firewall-cmd --zone=internal --add-service=mysql --permanent
firewall-cmd --zone=internal --add-port=10000-10010/tcp --permanent
firewall-cmd --reload
```

`internal` also ships with `mdns`, `samba-client` and `dhcpv6-client`. None of those are wanted on a server, so take them out:

```bash
firewall-cmd --zone=internal --remove-service=mdns --permanent
firewall-cmd --zone=internal --remove-service=samba-client --permanent
firewall-cmd --zone=internal --remove-service=dhcpv6-client --permanent
firewall-cmd --reload
```

Now open a second SSH session from one of the trusted addresses and check it works. Keep it open.

## Strip the public zone

Services the installer added:

```bash
firewall-cmd --zone=public --remove-service=smtp --permanent
firewall-cmd --zone=public --remove-service=ftp --permanent
firewall-cmd --zone=public --remove-service=pop3 --permanent
firewall-cmd --zone=public --remove-service=pop3s --permanent
firewall-cmd --zone=public --remove-service=imap --permanent
firewall-cmd --zone=public --remove-service=imaps --permanent
firewall-cmd --zone=public --remove-service=mysql --permanent
firewall-cmd --zone=public --remove-service=dhcpv6-client --permanent
firewall-cmd --zone=public --remove-service=ssh --permanent
```

And the raw ports:

```bash
firewall-cmd --zone=public --remove-port=587/tcp --permanent
firewall-cmd --zone=public --remove-port=53/tcp --permanent
firewall-cmd --zone=public --remove-port=53/udp --permanent
firewall-cmd --zone=public --remove-port=20/tcp --permanent
firewall-cmd --zone=public --remove-port=2222/tcp --permanent
firewall-cmd --zone=public --remove-port=10000-10010/tcp --permanent
firewall-cmd --zone=public --remove-port=10000-10100/tcp --permanent
firewall-cmd --zone=public --remove-port=20000/tcp --permanent
firewall-cmd --reload
```

Some of these will say `NOT_ENABLED` because the installer's version or options differed. That's harmless; the point is that none of them stay open. Port 10000 is Webmin, 20000 is Usermin, 587 is mail submission and 53 is DNS. If the server doesn't host mail or DNS for anyone, none of them should be public.

What should be left:

```bash
firewall-cmd --zone=public --list-all
```

```text
public (active)
  target: default
  interfaces: eth0
  services: http https
  ports:
  ...
```

If `http` and `https` aren't there, add them:

```bash
firewall-cmd --zone=public --add-service=http --add-service=https --permanent
firewall-cmd --reload
```

## Things to watch

`--permanent` only writes the configuration; it doesn't change the running firewall until `--reload`. Without `--permanent` the change is live but gone at the next reload or reboot. I use `--permanent` everywhere and reload once at the end, so the running state and the saved state never drift apart. If you've been testing with runtime-only rules and want to keep them, `firewall-cmd --runtime-to-permanent` saves the current state.

Removing a service from firewalld doesn't stop it. Dovecot, BIND and ProFTPD will still be running and listening, just unreachable. If you don't need them, stop and disable them as well, so a mistake in the firewall later doesn't expose them again.

If your own address changes (a new office line, working from home), you'll need a way back in that doesn't depend on it: the provider's console, or a second trusted address such as a VPN endpoint or bastion host in the source list.
