Skip to content

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 you ubuntu 22.04 or rhel 9.3 in a parseable format.

Gotcha: Alpine uses musl libc, not glibc. 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

  1. Inventory differences:
  2. SELinux → AppArmor (or disable and use only network-level controls)
  3. firewalld → ufw
  4. nmcli → netplan
  5. dnf → apt
  6. /etc/sysconfig/ → /etc/default/, /etc/netplan/
  7. subscription-manager → Ubuntu Pro (pro attach)

  8. Ansible conversion:

    # Replace dnf with apt or use ansible.builtin.package (generic)
    # Replace firewalld with community.general.ufw
    # Replace nmcli tasks with netplan template deploys
    # Replace seboolean with AppArmor profile management
    

  9. 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 denied on both systems.

Gotcha: Ubuntu uses /etc/netplan/ for network configuration, but many guides still reference /etc/network/interfaces. If you edit interfaces on a modern Ubuntu (18.04+), netplan silently overrides it on reboot. Always check which system is active: networkctl status or netplan 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/syslog returns 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. apt uses SAT solvers and prefers upgrading; dnf uses 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 with CGO_ENABLED=0 for 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.package instead of apt/dnf for common packages. For packages with different names across distros (like build-essential vs @"Development Tools"), use ansible_os_family in conditionals.