Linux Distribution Comparison — Street Ops¶
Quick-reference operational patterns for working across distros.
Identifying What You're On¶
# Universal
cat /etc/os-release
# Specific
lsb_release -a # Debian/Ubuntu
cat /etc/redhat-release # RHEL/CentOS/Fedora
cat /etc/debian_version # Debian
cat /etc/alpine-release # Alpine
hostnamectl # systemd-based (most modern distros)
One-liner: Quick one-liner for scripts that need to branch on distro:
. /etc/os-release && echo "$ID $VERSION_ID"— gives youubuntu 22.04orrhel 9.3in a parseable format.Gotcha: Alpine uses
musllibc, notglibc. Binaries compiled on Ubuntu/RHEL will segfault or fail to start on Alpine. This is the #1 reason "it works in dev but not in the container."
Cross-Distro Cheat Sheet¶
"Install Package X" on Any Distro¶
# Detect and install
if command -v apt &>/dev/null; then
sudo apt update && sudo apt install -y "$PKG"
elif command -v dnf &>/dev/null; then
sudo dnf install -y "$PKG"
elif command -v zypper &>/dev/null; then
sudo zypper install -y "$PKG"
elif command -v apk &>/dev/null; then
apk add "$PKG"
elif command -v pacman &>/dev/null; then
sudo pacman -S --noconfirm "$PKG"
fi
Remember: Package manager mnemonic: Debian = dpkg/apt, Red Hat = rpm/dnf, Alpine = apk, Arch = pacman. The "D-R-A-A" stack. If you can remember the distro family, you know the package manager.
"What's Listening on Port 80" on Any Distro¶
# Works everywhere (one of these will exist)
ss -tlnp | grep :80
# or
netstat -tlnp | grep :80
# or
lsof -i :80
Service Management (systemd — works on all major distros)¶
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx
journalctl -u nginx -f
Distro Migration Playbook¶
RHEL/CentOS → Ubuntu LTS¶
- Inventory differences:
- SELinux → AppArmor (or disable and use only network-level controls)
- firewalld → ufw
- nmcli → netplan
- dnf → apt
- /etc/sysconfig/ → /etc/default/, /etc/netplan/
-
subscription-manager → Ubuntu Pro (pro attach)
-
Ansible conversion:
-
Test thoroughly: Different default kernel tuning, different default open ports, different logging paths.
Default trap: SELinux is enforcing by default on RHEL; AppArmor is enforcing by default on Ubuntu. When migrating, your app may silently fail because the new MAC system blocks operations the old one allowed. Check
dmesg | grep -i deniedon both systems.Gotcha: Ubuntu uses
/etc/netplan/for network configuration, but many guides still reference/etc/network/interfaces. If you editinterfaceson a modern Ubuntu (18.04+), netplan silently overrides it on reboot. Always check which system is active:networkctl statusornetplan get.
CentOS 7 → AlmaLinux/Rocky 9¶
Easier — same family. Use leapp or elevate tools:
# ELevate (AlmaLinux project)
sudo yum install -y elevate-release leapp-upgrade
sudo leapp preupgrade
sudo leapp upgrade
Package Name Differences¶
| Software | Debian/Ubuntu | RHEL/Fedora | Alpine |
|---|---|---|---|
| Apache | apache2 | httpd | apache2 |
| Nginx | nginx | nginx | nginx |
| PHP | php-fpm | php-fpm | php-fpm |
| Python 3 | python3 | python3 | python3 |
| MySQL client | mysql-client | mysql | mariadb-client |
| PostgreSQL | postgresql | postgresql-server | postgresql |
| OpenSSH server | openssh-server | openssh-server | openssh |
| Cron | cron | cronie | busybox (crond) |
| NTP | chrony / systemd-timesyncd | chrony | chrony |
| Vim | vim | vim-enhanced | vim |
| curl | curl | curl | curl |
| Development tools | build-essential | @"Development Tools" | build-base |
Config File Location Differences¶
| Config | Debian/Ubuntu | RHEL/Fedora |
|---|---|---|
| Apache vhosts | /etc/apache2/sites-available/ | /etc/httpd/conf.d/ |
| Apache enable site | a2ensite | symlink in conf.d/ |
| Nginx sites | /etc/nginx/sites-available/ | /etc/nginx/conf.d/ |
| Network config | /etc/netplan/ | /etc/NetworkManager/ |
| Default shell env | /etc/default/ | /etc/sysconfig/ |
| Firewall | ufw rules in /etc/ufw/ | /etc/firewalld/ |
| Sudo | /etc/sudoers.d/ | /etc/sudoers.d/ (same) |
| Syslog | /var/log/syslog | /var/log/messages |
| Auth log | /var/log/auth.log | /var/log/secure |
| Cron spool | /var/spool/cron/crontabs/ | /var/spool/cron/ |
Debug clue: When log analysis fails, check the syslog path first.
grep -r "error" /var/log/syslogreturns nothing on RHEL because the file is/var/log/messages. Same content, different name — muscle memory from one distro burns you on another.Under the hood: Package managers resolve dependencies differently.
aptuses SAT solvers and prefers upgrading;dnfuses libsolv and prefers minimal changes. This means the same "install X" command can pull in different dependency trees on each distro.
War story: A team built CI on Ubuntu, deployed containers on Alpine. Everything passed in CI, but the production container segfaulted on startup. Root cause: a Go binary compiled with CGO on glibc (Ubuntu) called
getaddrinfo(), which does not exist in musl (Alpine). Fix: compile withCGO_ENABLED=0for static binaries, or use the same base image in CI and production.
Fleet Audit: What Distros Am I Running?¶
# Ansible one-liner
ansible all -m setup -a "filter=ansible_distribution*" | \
grep -E "distribution|version"
# Or gather into a report
ansible all -m shell -a "cat /etc/os-release | grep PRETTY_NAME" \
--one-line | sort
Scale note: In mixed-distro fleets, use
ansible.builtin.packageinstead ofapt/dnffor common packages. For packages with different names across distros (likebuild-essentialvs@"Development Tools"), useansible_os_familyin conditionals.