SOC 2 is an attestation report issued by an independent CPA firm about how an organization protects customer data, based on the AICPA Trust Services Criteria. It does not prescribe specific settings: you define controls, and the auditor checks that they are designed well (Type I) and that they operated consistently over a period, usually 3 to 12 months (Type II). For the servers, that means two jobs: configure the controls, and keep evidence that they were in place the whole time. In this tutorial you will implement the most commonly audited Linux controls on Ubuntu 24.04 and automate the evidence collection.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, and a non-root user with
sudoprivileges who logs in with an SSH key. - A central log destination that server administrators cannot modify, such as a dedicated syslog server or a SIEM. This guide uses
logs.your_domainon TCP port 514 as a placeholder. - The scope of your audit and your written policies (access control, change management, incident response). The commands below implement policies; they do not replace them.
How the criteria map to a Linux server
Most SOC 2 reports cover the Security category (the Common Criteria, CC), often with Availability. These are the criteria that auditors test directly on servers:
| Criterion | What the auditor looks for | Linux control in this guide |
|---|---|---|
| CC6.1 | Access restricted to authorized, identifiable users | Individual accounts, SSH keys, no root login |
| CC6.2 / CC6.3 | Access granted and removed through a process | Group-based access, review of accounts |
| CC7.2 | Monitoring of system activity and anomalies | auditd rules, central logging |
| CC7.1 / CC8.1 | Vulnerabilities and changes tracked | Automatic security updates, package history |
| A1.2 | Backups and recovery | Backups with tested restores |
Step 1 - Enforcing individual accounts and key-based SSH
Every action on a server must be traceable to one person, so shared accounts and direct root logins are not acceptable. Create a group that controls who may log in over SSH:
sudo groupadd sshusers
Create one account per administrator, replacing alice with the real user name, and add it to the SSH group and to sudo:
sudo adduser alice
sudo usermod -aG sshusers,sudo alice
Add the user's public key to /home/alice/.ssh/authorized_keys with permissions 600, owned by the user. Also add your own account to the group before the next change, or you will lock yourself out:
sudo usermod -aG sshusers "$USER"
Now restrict SSH. Create a drop-in file; on Ubuntu 24.04 files in sshd_config.d are read first and the first value wins, so a low number takes precedence:
sudo nano /etc/ssh/sshd_config.d/10-soc2.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowGroups sshusers
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSE
LogLevel VERBOSE records the fingerprint of the key used for each login, which lets you attribute a session to a specific key. Test and apply:
sudo sshd -t && sudo systemctl restart ssh
Keep your current session open and confirm in a new terminal that you can still log in. Then check the effective configuration:
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|allowgroups)'
permitrootlogin no
passwordauthentication no
allowgroups sshusers
For access reviews, this command lists who can log in and who has sudo, which is exactly what an auditor asks for each quarter:
getent group sshusers sudo
When someone leaves, lock the account and expire it the same day rather than deleting it, so their audit trail keeps a valid owner:
sudo usermod --lock --expiredate 1 alice
Step 2 - Recording privileged activity with auditd
The Linux audit framework records security events in the kernel, including every command run as root by a real user. Install the daemon and its plugins (you will need the syslog plugin in Step 3):
sudo apt install auditd audispd-plugins
Create a rules file for the events auditors ask about most: changes to identity and privilege files, SSH and cron configuration, and commands executed as root:
sudo nano /etc/audit/rules.d/50-soc2.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/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k privilege
-w /etc/sudoers.d/ -p wa -k privilege
## Remote access and scheduled tasks
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/sshd_config.d/ -p wa -k sshd_config
-w /etc/crontab -p wa -k cron
-w /etc/cron.d/ -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
## Commands run as root by logged-in users
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k root_commands
-a always,exit -F arch=b32 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k root_commands
The auid filter keeps the log focused: it matches commands whose login user is a real person (UID 1000 and above), even after sudo, and skips background services. Load the rules and list them:
sudo augenrules --load
sudo auditctl -l
Test that a change is recorded. Creating and deleting a group writes to /etc/group, so it triggers the identity key:
sudo groupadd audittest && sudo groupdel audittest
sudo ausearch -k identity -i --start recent | tail -n 5
The output includes the file, the command and auid= with the name of the person who ran it. For a summary of recent activity by user, use:
sudo aureport -x --summary -i
Step 3 - Shipping logs to a central server
Logs stored only on the server can be altered by anyone who compromises it, and they vanish if the server is rebuilt. Ship them to the central destination with rsyslog, which is installed on Ubuntu by default. Create a forwarding rule with a disk-assisted queue, so messages are kept if the log server is unreachable:
sudo nano /etc/rsyslog.d/60-forward.conf
*.* action(
type="omfwd"
target="logs.your_domain"
port="514"
protocol="tcp"
queue.type="LinkedList"
queue.filename="fwd_central"
queue.saveOnShutdown="on"
queue.maxDiskSpace="1g"
action.resumeRetryCount="-1"
)
auditd writes to its own file, so also send audit records to syslog. Enable the audit syslog plugin:
sudo sed -i 's/^active = no/active = yes/' /etc/audit/plugins.d/syslog.conf
Check the syntax and restart both services:
sudo rsyslogd -N1
sudo systemctl restart rsyslog auditd
Send a test message and confirm it arrives on the central server:
logger -t soc2-test "central logging test from $(hostname)"
Notethis forwards logs in plain text over TCP. If the log server is not on a private network, encrypt the transport with TLS (the
rsyslog-gnutlspackage) or use your SIEM's agent. Auditors often ask for 12 months of log retention; configure that on the central server.
Step 4 - Patching and change history
Auditors test that security updates are applied within the timeframe your policy states. Ubuntu 24.04 server installs unattended-upgrades with security updates enabled; confirm it is installed and active:
sudo apt install unattended-upgrades
cat /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
If the file is missing or the values are 0, enable it:
sudo dpkg-reconfigure --priority=low unattended-upgrades
The evidence of patching and of every manual package change is already on disk, so you do not need extra tooling:
/var/log/unattended-upgrades/unattended-upgrades.log: every automatic update run./var/log/apt/history.log: every apt operation, with the date and theRequested-Byuser./var/log/dpkg.log: every package installed, upgraded or removed.
Show the most recent changes with the user who made them:
grep -E '^(Start-Date|Commandline|Requested-By)' /var/log/apt/history.log | tail -n 9
Application and configuration changes should go through your version control and review process (pull requests, CI pipelines, tickets); that is where CC8.1 evidence for them comes from. On the server, the auditd rules from Step 2 catch changes made outside that process.
Step 5 - Checking encryption in transit and at rest
CC6.7 asks that data is protected in transit. Check that each public service offers only modern TLS versions. Replace your_domain:
openssl s_client -connect your_domain:443 -servername your_domain -tls1_1 </dev/null
A handshake failure is the expected result, because TLS 1.1 must be rejected. Then check that TLS 1.2 works and read the certificate expiry date:
openssl s_client -connect your_domain:443 -servername your_domain -tls1_2 </dev/null 2>/dev/null | openssl x509 -noout -enddate
notAfter=Dec 14 08:12:45 2026 GMT
For data at rest, list block devices and look for crypt devices if your policy requires disk encryption on the server itself:
lsblk -o NAME,TYPE,FSTYPE,MOUNTPOINTS
If your provider encrypts storage at the infrastructure level, that is documented in the provider's own SOC 2 or ISO 27001 report, which you reference as a subservice organization instead of proving it from inside the VM.
Step 6 - Collecting evidence automatically
For a Type II audit you need proof that controls were in place during the whole period, not only on the day of the audit. A monthly snapshot of the configuration answers most auditor requests. Create the collection script:
sudo nano /usr/local/sbin/soc2-evidence
#!/usr/bin/env bash
set -euo pipefail
outdir="/var/lib/soc2-evidence/$(date +%Y-%m)"
install -d -m 0700 "$outdir"
cd "$outdir"
getent passwd > users.txt
getent group sshusers sudo > privileged-groups.txt
cat /etc/sudoers.d/* > sudoers-d.txt 2>/dev/null || true
sshd -T > sshd-effective.txt
auditctl -l > audit-rules.txt
last -F -n 500 > logins.txt || true
journalctl -u ssh --since "-31 days" --no-pager | grep -E 'Accepted|Failed' > ssh-auth.txt || true
dpkg-query -W -f='${Package} ${Version}\n' > packages.txt
cp /var/log/apt/history.log apt-history.txt
systemctl list-units --type=service --state=running --no-pager > services.txt
ss -tulpn > listening-ports.txt
ufw status verbose > firewall.txt || true
sha256sum -- *.txt > SHA256SUMS
echo "Evidence written to $outdir"
The SHA256SUMS file lets you show later that the snapshot was not edited. Make the script executable and run it once:
sudo chmod 750 /usr/local/sbin/soc2-evidence
sudo /usr/local/sbin/soc2-evidence
sudo ls /var/lib/soc2-evidence/"$(date +%Y-%m)"
SHA256SUMS apt-history.txt audit-rules.txt firewall.txt listening-ports.txt logins.txt packages.txt
privileged-groups.txt services.txt ssh-auth.txt sshd-effective.txt sudoers-d.txt users.txt
Schedule it on the first day of each month with a systemd timer. Create the service:
sudo nano /etc/systemd/system/soc2-evidence.service
[Unit]
Description=Collect monthly SOC 2 evidence
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/soc2-evidence
Then the timer:
sudo nano /etc/systemd/system/soc2-evidence.timer
[Unit]
Description=Run SOC 2 evidence collection monthly
[Timer]
OnCalendar=monthly
Persistent=true
[Install]
WantedBy=timers.target
Enable it and confirm the next run:
sudo systemctl daemon-reload
sudo systemctl enable --now soc2-evidence.timer
systemctl list-timers soc2-evidence.timer
Copy each month's folder to the same write-protected storage as your logs, so the evidence survives a server rebuild.
Troubleshooting
auditctl -l shows "No rules". augenrules did not load the file, usually because of a syntax error. Run sudo augenrules --check and sudo augenrules --load and read the error. Also check that the rule set is not locked with -e 2 from an earlier configuration, which requires a reboot to change.
No logs arrive at the central server. Confirm the server can reach it with nc -zv logs.your_domain 514, check sudo journalctl -u rsyslog -n 50 for connection errors, and make sure the firewall on the log server allows the connection.
A user cannot log in after Step 1. They are probably not in sshusers. Check with id their_user and add them with sudo usermod -aG sshusers their_user. sudo journalctl -u ssh -n 50 shows the reason for each rejection.
Conclusion
Your Ubuntu 24.04 server now enforces individual key-based access, records privileged activity with auditd, ships its logs off the host, applies security updates automatically, and produces a monthly, checksummed evidence snapshot. Next, apply the same configuration to every in-scope server through configuration management, schedule quarterly access reviews using the privileged groups report, and add a documented, tested backup restore to cover the Availability criteria.
