The NIST Cybersecurity Framework (CSF) is a voluntary framework for managing cybersecurity risk. Version 2.0, published in February 2024, organizes outcomes into six functions: Govern, Identify, Protect, Detect, Respond and Recover. In this tutorial you will translate those functions into concrete controls on an Ubuntu 24.04 server and collect the output as evidence, with each step labeled with the CSF 2.0 subcategory it supports.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges and SSH key authentication already working.
  • A second host reachable over SSH (SFTP) to store backups.
  • A copy of the CSF 2.0 core (free at nist.gov) if you want to look up each subcategory in full.

How the CSF functions map to a Linux server

The CSF describes outcomes, not tools. The table shows how each function turns into work on a single server:

FunctionGoalControls in this guide
Govern (GV)Set strategy, roles and policyWritten policy and ownership (outside the server)
Identify (ID)Know your assets and risksPackage, service and port inventory
Protect (PR)Limit the impact of an eventSSH hardening, firewall, automatic patching, permissions
Detect (DE)Find events quicklyauditd, AIDE file integrity, fail2ban
Respond (RS)Contain and analyze incidentsEvidence collection procedure
Recover (RC)Restore normal operationsEncrypted backups with verified restores

Govern is organizational: someone must own the risk, approve policies and decide the target profile. Record those decisions before you start, because they define which of the following controls are mandatory for you.

Step 1 - Building an asset inventory (Identify)

Subcategories ID.AM-01 and ID.AM-02 require inventories of hardware and of software and services. Create a directory for evidence that only root can read:

sudo install -d -m 700 /var/lib/nist-evidence

Record the installed packages with their versions:

dpkg-query -W -f='${Package}\t${Version}\n' | sudo tee /var/lib/nist-evidence/packages.tsv > /dev/null
wc -l /var/lib/nist-evidence/packages.tsv
612 /var/lib/nist-evidence/packages.tsv

Record the enabled services and the ports that accept connections. Every listening port is part of your attack surface:

systemctl list-unit-files --type=service --state=enabled --no-pager | sudo tee /var/lib/nist-evidence/services.txt > /dev/null
sudo ss -tulpn | sudo tee /var/lib/nist-evidence/listening.txt
Netid State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp   UNCONN 0      0      127.0.0.54:53      0.0.0.0:*         users:(("systemd-resolve",pid=512,fd=16))
tcp   LISTEN 0      4096   0.0.0.0:22         0.0.0.0:*         users:(("sshd",pid=880,fd=3))

Review each line and ask whether that service is needed. Anything you cannot justify is a risk to remove in the Protect step. Also look for binaries with the SUID bit, which run with the owner's privileges and are a common escalation path (ID.RA-01, vulnerabilities identified):

sudo find / -xdev -perm -4000 -type f 2>/dev/null | sudo tee /var/lib/nist-evidence/suid.txt

Compare this list with a fresh install; an unexpected entry deserves investigation.

Step 2 - Hardening access (Protect)

PR.AA-01 and PR.AA-05 cover managed identities and least-privilege access. Start by listing accounts that can log in:

awk -F: '$7 !~ /(nologin|false)$/ {print $1, $7}' /etc/passwd
root /bin/bash
your_user /bin/bash

Lock any account on that list that has no current owner with sudo usermod -L -e 1 username. Then restrict SSH to key-based logins. Create a drop-in file; Ubuntu 24.04 reads sshd_config.d files alphabetically and the first value wins, so the 10- prefix overrides 50-cloud-init.conf:

sudo nano /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
X11Forwarding no
LogLevel VERBOSE

Validate and reload:

sudo sshd -t
sudo systemctl reload ssh

Keep your current session open and confirm you can log in from a second terminal before closing it.

Step 3 - Protecting the network and keeping software patched (Protect)

PR.IR-01 asks that networks and environments be protected from unauthorized access. Allow only what the inventory justified. For a web server:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status verbose
Status: active
Default: deny (incoming), allow (outgoing), disabled (routed)
To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
80,443/tcp                 ALLOW IN    Anywhere

PR.PS-02 requires software to be maintained. Enable automatic security updates:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Confirm the setting and see what is pending:

cat /etc/apt/apt.conf.d/20auto-upgrades
apt list --upgradable 2>/dev/null
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

For data at rest (PR.DS-01), check whether volumes are encrypted. A crypto_LUKS entry means the device is encrypted:

lsblk -f

If sensitive data lives on an unencrypted disk, plan a migration to a LUKS volume.

Step 4 - Monitoring activity with auditd (Detect)

DE.CM-03 and DE.CM-09 cover monitoring of personnel activity and of computing hardware and software. Install the audit daemon:

sudo apt install auditd

Create a rules file:

sudo nano /etc/audit/rules.d/nist-csf.rules
## Identity and privilege changes
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k privilege
-w /etc/sudoers.d/ -p wa -k privilege

## Remote access configuration
-w /etc/ssh/sshd_config -p wa -k sshd
-w /etc/ssh/sshd_config.d/ -p wa -k sshd

## Network configuration changes
-w /etc/netplan/ -p wa -k network
-a always,exit -F arch=b64 -S sethostname,setdomainname -k network

## Commands executed as root by logged-in users
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k root_cmd

Load the rules and check them:

sudo augenrules --load
sudo auditctl -l | wc -l

The count should match the number of rules in the file. Test one by adding a temporary user, then search the log:

sudo useradd audit-test && sudo userdel audit-test
sudo ausearch -k identity -i --start recent | tail -n 4

You should see PATH records for /etc/passwd with your username in the auid field.

Step 5 - Detecting unauthorized file changes with AIDE (Detect)

PR.DS-01 and DE.CM-09 also cover integrity. AIDE (Advanced Intrusion Detection Environment) stores hashes of system files and reports anything that changes. Install it:

sudo apt install aide

Build the initial database. This takes several minutes and should be done on a system you trust:

sudo aideinit

aideinit writes the database to /var/lib/aide/aide.db. Run a check to confirm it works:

sudo aide --check --config /etc/aide/aide.conf
AIDE found NO differences between database and filesystem. Looks okay!!

Change something harmless, such as sudo touch /etc/hosts, and run the check again: AIDE now reports the modified file. After planned changes (for example package upgrades), review the report and rebuild the baseline with sudo aideinit -y -f.

Step 6 - Blocking brute-force attempts with fail2ban (Detect)

DE.CM-01 asks you to monitor networks for adverse events. fail2ban watches the SSH log and bans addresses after repeated failures:

sudo apt install fail2ban

Create a local configuration so package updates do not overwrite your changes:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
backend = systemd

Enable the service and check the jail:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     3
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

Step 7 - Preparing incident evidence collection (Respond)

RS.MA-01 (incident response plan executed) and RS.AN-03 (analysis performed) depend on collecting volatile data before it disappears. Write the procedure down now, not during an incident. Open a root shell so every command can write to the evidence folder:

sudo -i

Create a dated folder for the incident:

mkdir -p /root/incident-$(date +%F) && cd /root/incident-$(date +%F)

Capture sessions, processes and network state first, because they are lost on reboot:

w > sessions.txt
ps auxwwf > processes.txt
ss -tupan > connections.txt

Then capture logs and recent changes:

journalctl --since "-24h" -o short-iso > journal-24h.txt
ausearch --start today -i > audit-today.txt
find /etc /usr/local/bin -xdev -mtime -2 -type f > recent-changes.txt
aide --check --config /etc/aide/aide.conf > aide.txt

Finally, hash the evidence so you can prove it was not altered later, and leave the root shell:

sha256sum *.txt > SHA256SUMS
exit

Copy the directory to the backup host and do not reboot or reinstall the server until someone has decided on containment.

Step 8 - Automating verified backups (Recover)

PR.DS-11 requires backups to be created, protected, maintained and tested, and RC.RP-03 requires the integrity of backups to be verified before restoring. restic encrypts and deduplicates backups. Install it and create a password file:

sudo apt install restic
sudo mkdir -p /etc/restic
sudo sh -c 'openssl rand -base64 32 > /etc/restic/password'
sudo chmod 600 /etc/restic/password

Save the password in your password manager as well. Initialize the repository on the backup host, replacing backupuser and backup_host:

sudo restic -r sftp:backupuser@backup_host:/srv/restic/server1 --password-file /etc/restic/password init

Create the backup service:

sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=restic backup

[Service]
Type=oneshot
Environment=RESTIC_REPOSITORY=sftp:backupuser@backup_host:/srv/restic/server1
Environment=RESTIC_PASSWORD_FILE=/etc/restic/password
ExecStart=/usr/bin/restic backup /etc /home /var/www /var/lib/nist-evidence
ExecStart=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
ExecStart=/usr/bin/restic check

And a daily timer:

sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Daily restic backup

[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=15m
Persistent=true

[Install]
WantedBy=timers.target

Enable the timer, run a first backup and check the result:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service -n 5 --no-pager

The last lines should include no errors were found. Test a restore of /etc into a temporary directory, which is what RC.RP-03 really asks for:

sudo restic -r sftp:backupuser@backup_host:/srv/restic/server1 --password-file /etc/restic/password restore latest --target /tmp/restore-test --include /etc
sudo ls /tmp/restore-test/etc | head -n 3
sudo rm -rf /tmp/restore-test

Control mapping reference

Use this table in your CSF profile to link each subcategory to its evidence:

CSF 2.0 subcategoryControl on the serverEvidence
ID.AM-01, ID.AM-02Package, service and port inventory/var/lib/nist-evidence/*.txt
ID.RA-01SUID review, pending updatessuid.txt, apt list --upgradable
PR.AA-01, PR.AA-05Named accounts, no root SSH, key-only loginsshd -T, /etc/passwd review
PR.IR-01UFW default denyufw status verbose
PR.PS-02Unattended security upgrades20auto-upgrades
PR.DS-01LUKS volumes, AIDElsblk -f, AIDE reports
PR.DS-11, RC.RP-03restic backups with check and restore testjournal of restic-backup.service
DE.CM-01fail2ban on SSHfail2ban-client status sshd
DE.CM-03, DE.CM-09auditd rulesausearch and aureport output
RS.MA-01, RS.AN-03Evidence collection procedureIncident folders with SHA256SUMS

Troubleshooting

AIDE reports hundreds of changes after every upgrade. That is expected: packages replace files. Review the report, then rebuild the baseline with sudo aideinit -y -f. To ignore a volatile path, add a line such as !/var/log/myapp to a file in /etc/aide/aide.conf.d/.

Audit logs fill the disk. Edit /etc/audit/auditd.conf, set max_log_file = 50, num_logs = 10 and max_log_file_action = ROTATE, then run sudo service auditd restart.

fail2ban shows no failures. Make sure the jail uses backend = systemd; Ubuntu 24.04 logs SSH to the journal and the jail will not see events from a log file path that does not match.

Conclusion

You mapped each CSF 2.0 function to working controls on Ubuntu 24.04: an asset inventory, SSH and firewall hardening, automatic patching, audit and integrity monitoring, a written evidence procedure and backups that are verified by restore. Next, document your current and target profiles under the Govern function, forward auditd and fail2ban events to a central SIEM, and review the evidence directory on a fixed schedule, for example quarterly.