Where to go next

What a fresh install exposes, and what to learn next.

Beginner10 min · lesson 29 of 29

You can now find your way around a Linux server, read and set permissions, manage users and sudo, run services and read their logs, look after disks, networking, archives and packages, and work through a failure layer by layer. This last lesson points those skills at a freshly installed server to see what it exposes and what it already protects, which is exactly where the hardening course begins. It then describes the three courses that follow and what each one expects you to know.

What listens on the network

The terminals in this section come from the course's default lab machine: an Ubuntu Server 26.04 cloud image with its updates applied and nothing else installed, plus two accounts the lab needs, the VM tool's lima and the course's deploy. First, what listens for connections. "Networking basics" used ss -tlnp for TCP; adding -u includes UDP, where some services listen without any connection at all:

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ sudo ss -tulpn
Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess udp UNCONN 0 0 0.0.0.0:5353 0.0.0.0:* users:(("systemd-resolve",pid=471,fd=13)) udp UNCONN 0 0 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=471,fd=21)) udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=471,fd=19)) udp UNCONN 0 0 192.168.5.15%eth0:68 0.0.0.0:* users:(("systemd-network",pid=775,fd=36)) udp UNCONN 0 0 127.0.0.1:323 0.0.0.0:* users:(("chronyd",pid=930,fd=4)) udp UNCONN 0 0 [::]:5353 [::]:* users:(("systemd-resolve",pid=471,fd=14)) udp UNCONN 0 0 [::1]:323 [::]:* users:(("chronyd",pid=930,fd=5)) tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=471,fd=20)) tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=882,fd=3),("systemd",pid=1,fd=260)) tcp LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=471,fd=22)) tcp LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=882,fd=4),("systemd",pid=1,fd=261))

Read the Local Address column. Port 22 on 0.0.0.0 and [::] is SSH on every IPv4 and IPv6 address, held by systemd, which listens for the first connection, and by sshd. The DNS stub of systemd-resolved listens on 127.0.0.53 and 127.0.0.54, and chrony's command port 323 on 127.0.0.1 and [::1]; loopback addresses are reachable only from the machine itself. UDP port 68 on the network interface's address is the DHCP client of systemd-networkd, which receives the machine's address lease. UDP port 5353, multicast DNS, is there only because the lab's VM tool turns it on in systemd-resolved; a default Ubuntu Server does not listen on it. So from the network, a fresh server offers SSH and nothing else.

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ systemctl list-sockets --no-pager "*ssh*"
LISTEN UNIT ACTIVATES 0.0.0.0:22 ssh.socket ssh.service [::]:22 ssh.socket ssh.service vsock::22 sshd-vsock.socket sshd@3-2-3:22-2:1612536735.service /run/ssh-unix-local/socket sshd-unix-local.socket - …

systemd's own list adds two SSH sockets that ss -tulpn does not show, and neither is reachable from the network. The vsock one exists only inside a virtual machine (vsock is a channel between a VM and its host); the local Unix socket is bound by systemd on any machine with sshd installed.

Who can log in, and who can become root

Accounts with a real login shell are the ones a person can use, and membership of sudo decides who can act as root:

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ awk -F: '$7 !~ /(nologin|false)$/' /etc/passwd
root:x:0:0:root:/root:/bin/bash sync:x:4:65534:sync:/bin:/bin/sync lima:x:502:1000:Lima VM user:/home/lima.linux:/bin/bash deploy:x:1001:1001:Deploy user:/home/deploy:/bin/bash
$ getent group sudo adm
sudo:x:27:deploy adm:x:4:syslog,deploy
$ sudo ls /etc/sudoers.d
90-cloud-init-users 90-secopslog-lab README

Four accounts have a login shell. sync is historical: its "shell" is /bin/sync, which flushes data to disk and exits, so the account cannot give anyone a shell. lima belongs to the VM tool; on a cloud server the same place is taken by ubuntu, the default user that cloud-init creates. deploy is in sudo, so the %sudo rule in /etc/sudoers makes it an administrator, and in adm, so it reads the logs. Two files in /etc/sudoers.d matter. 90-cloud-init-users is written by cloud-init: Ubuntu's cloud configuration gives its default user password-less sudo, which the VM tool uses for lima. 90-secopslog-lab gives deploy password-less sudo so the labs can run unattended; your own servers should not have it. Without such a file, %sudo asks for the user's password.

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ sudo find / -xdev -perm -4000 -type f | sort
/usr/bin/chfn /usr/bin/chsh /usr/bin/fusermount3 /usr/bin/gpasswd /usr/bin/mount /usr/bin/newgrp /usr/bin/ntfs-3g /usr/bin/passwd /usr/bin/su /usr/bin/sudo.ws /usr/bin/umount /usr/lib/cargo/bin/su /usr/lib/cargo/bin/sudo /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/openssh/ssh-keysign

-xdev keeps find on the root filesystem, and sort makes the list easy to compare with a later one. Fifteen programs carry the set-user-ID bit and run as root whoever starts them: the account tools (passwd, chsh, chfn, gpasswd, newgrp), su and sudo (sudo-rs under /usr/lib/cargo/bin, and classic sudo installed as sudo.ws), the mount helpers, a D-Bus helper and ssh-keysign. The lesson on SUID, SGID and the sticky bit explained why each exists; any program that joins this list later deserves an explanation. File capabilities, the narrower alternative from the same lesson, need their own search, because find -perm -4000 does not see them:

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ sudo getcap -r / 2>/dev/null
/usr/bin/ping cap_net_raw=ep /usr/bin/mtr-packet cap_net_raw=ep /usr/lib/aarch64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_bind_service,cap_net_admin,cap_sys_nice=ep /usr/lib/snapd/snap-confine cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_setgid,cap_setuid,cap_sys_chroot,cap_sys_ptrace,cap_sys_admin,cap_sys_resource=p

ping and mtr-packet may open raw network sockets (cap_net_raw), and gst-ptp-helper belongs to the GStreamer media libraries (on an x86_64 server its path has x86_64-linux-gnu in place of aarch64-linux-gnu). snap-confine, the helper snapd uses to start sandboxed snaps, holds a long list that includes cap_setuid and cap_sys_admin, which together are as good as root. Treat a new line here exactly like a new SUID program.

What already protects it, and what is missing

The remaining checks cover how SSH authenticates, whether anything filters traffic, and which security subsystems are running:

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ sudo sshd -T | grep -E '^(permitrootlogin|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication) '
permitrootlogin prohibit-password pubkeyauthentication yes passwordauthentication no kbdinteractiveauthentication no
$ sudo ufw status
Status: inactive
$ sudo nft list tables
table ip nat
$ sudo aa-status | head -n 3
apparmor module is loaded. 179 profiles are loaded. 103 profiles are in enforce mode.
$ systemctl status auditd
Unit auditd.service could not be found.

The SSH server accepts keys only: Ubuntu's cloud images switch password logins off in 60-cloudimg-settings.conf, and where no file sets it, OpenSSH's own default is to accept passwords. root may log in with a key, but never with a password (prohibit-password); logging in as yourself and using sudo leaves a record of who did what, which a shared root login does not. The firewall front-end ufw is installed but inactive, and nftables holds a single table, a NAT table the VM tool adds for its DNS forwarding, so the server itself filters nothing. AppArmor is loaded and enforcing 103 of its profiles, so hardening means confining more programs, never switching it off. auditd, the kernel audit daemon, is not installed on Ubuntu (RHEL installs and enables it).

deploy@web01 · Ubuntu 26.04 LTS (default install)
$ apt list --upgradable
… Listing... rust-coreutils/resolute-updates 0.10.0-1ubuntu2~26.04.1 arm64 [upgradable from: 0.8.0-0ubuntu3]
# … stands for a warning apt prints only when it runs without a terminal
$ systemctl is-active unattended-upgrades.service apt-daily-upgrade.timer
active active

One bug-fix update is waiting, held back by phasing as "Packages and updates" explained, and unattended-upgrades installs security fixes every day. A fresh server is not without records either: sudo writes the commands it runs to the journal and /var/log/auth.log, useradd logs every account it creates, and sshd-session logs every login. What auditd would add are records made by the kernel, such as every program executed or every change to a watched file, which do not depend on each program choosing to log.

A RHEL 10 server starts from different defaults, which the hardening course shows next to Ubuntu's at every step: SELinux in enforcing mode instead of AppArmor, auditd installed and running, the SSH server as sshd.service, enabled directly rather than started by a socket, and no automatic updates until you install dnf-automatic.

Where the track goes next

Every observation above is a starting point in the next course. Linux hardening (Intermediate) takes this default install and hardens it one control at a time: patching and reboots, fewer services and packages, accounts and PAM, sudo policy, the SSH server, trustworthy logs and auditd, file permissions, mount options and disk encryption, sandboxing services with systemd, host firewalls and nftables, kernel settings and modules, AppArmor and SELinux, integrity monitoring and CIS benchmarks. For each control it states the threat, the configuration, how to verify it, what it costs, and how to roll it back. It assumes this course and nothing else.

The findings in this lesson map directly onto its lessons. SSH listening on every address, with root allowed in by key, is the subject of "Hardening the SSH server" and "Minimise services and packages". An inactive ufw and a ruleset with no filtering lead to "Host firewalls: ufw and firewalld". Password-less sudo for the cloud user belongs to "Hardening sudo", the SUID list to "Auditing permissions, SUID/SGID and umask", and the missing auditd to "auditd rules that matter". The nightly security updates, and the question of when the server reboots to use them, are where the course begins, in "Patching and update strategy".

Advanced Linux security comes after that. It is a defensive course: how intruders escalate privileges, persist and hide on a Linux host, explained only as deeply as detection needs, then the audit pipeline, osquery, eBPF and Falco to catch them, threat hunting, live triage, forensic artefacts and incident response. It expects this course and the hardening course, whose auditd rules, sudo policy and module signing it builds on.

Advanced Linux internals and tooling is the operations path: how Linux works underneath and how to go from a symptom to a bottleneck. It covers a method for performance problems, processes and the scheduler, virtual memory, cgroups and namespaces, filesystems and the block layer, the network stack, strace, perf, flame graphs and eBPF tools, and tuning only after measuring. It needs only this course; the hardening course helps with the background on security modules and kernel settings, and the security course is not required.

After Linux essentials
Linux essentials, done
operate a server with confidence
next
Linux hardening
Intermediate; needs this course
after hardening
Advanced Linux security
needs essentials and hardening
any time
Advanced internals and tooling
needs essentials; hardening helps
The track order is essentials, hardening, security, internals.

Try this

Record a baseline you can compare later. On a lab server, save the listeners (without process IDs, which change), the accounts with a login shell, the sudo group, the SUID list and the file capabilities in one file: { sudo ss -Htuln; awk -F: '$7 !~ /(nologin|false)$/ {print $1}' /etc/passwd; getent group sudo; sudo find / -xdev -perm -4000 -type f | sort; sudo getcap -r / 2>/dev/null; } > ~/baseline-before.txt. The braces group the commands, so one > sends all their output to the file (the space after { and the ; before } are required), and -H leaves out ss's header line. Make a harmless change, sudo useradd -m -s /bin/bash ns-test, save the same output to ~/baseline-after.txt and run diff ~/baseline-before.txt ~/baseline-after.txt. diff prints only the differences: a location line such as 15a16 (after line 15 of the first file, line 16 of the second was added), then the added line marked with >, here > ns-test. Then find the change in the logs with journalctl -t useradd -n 2, and remove the account with sudo userdel -r ns-test. Keep the first file; as you work through the hardening course, the diff shows what each lesson changed.

Takeaway

Before you change a server, record what it exposes in a form you can diff. A change you can show with a command anyone can rerun is worth more than a change you remember making.

Quick check
01On a fresh Ubuntu 26.04 VM, sudo ss -tulpn shows SSH only on port 22, but systemctl list-sockets "*ssh*" also lists vsock::22 and /run/ssh-unix-local/socket. Do these give other machines on the network more ways in?
Correct — Neither is a network address, which is also why ss -tulpn, listing TCP and UDP, does not show them.
Incorrect — ss -tulpn lists TCP and UDP sockets only. These belong to other socket families that no network client can connect to.
Incorrect — vsock is a channel between a virtual machine and its own host, not a network protocol; other machines cannot address it.
Incorrect — systemd listens on these sockets from boot and starts sshd for each connection; that is socket activation.
02A colleague who has just finished this course wants to go straight to Advanced Linux security. What does that course assume they already know?
Incorrect — It builds on auditd rules, sudo policy and module signing rather than teaching them.
Incorrect — The security course explains eBPF from the defender's side itself; the internals course goes deeper but is optional.
Incorrect — The hardening course adds the controls, such as auditd rules and sudo policy, that the security course relies on.
Correct — The security course expects the essentials and the hardening controls it detects around.
03A month-old baseline and today's output differ by one line in the SUID section: /usr/local/bin/sync-helper. What should you do first?
Incorrect — Packages do not install into /usr/local, and any new SUID program needs an explanation before it is accepted.
Correct — Establish where it came from before changing anything; the answer decides whether it is an approved tool or an intrusion.
Incorrect — Deleting it destroys the evidence you need to decide what happened, and a reboot removes running processes you may need to see.
Incorrect — A baseline proves that something changed, not why. Understand the change first.

Related