The HIPAA Security Rule requires organizations that handle electronic Protected Health Information (ePHI) to implement technical safeguards: unique user identification, automatic logoff, audit controls, integrity protection, encryption and a tested backup plan. In this tutorial you will configure an Ubuntu 24.04 server to meet those technical safeguards for a directory that stores PHI. Each step maps to a specific section of 45 CFR 164.312 or 164.308 so you can use the output as evidence in your risk assessment.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS dedicated to the PHI workload, for example a CubePath VPS or dedicated server. Do not share it with unrelated applications.
  • A non-root user with sudo privileges and SSH key authentication already working.
  • A second, empty block device for PHI data (this guide uses /dev/vdb; check yours with lsblk).
  • A separate backup host reachable over SSH (SFTP) for encrypted off-site backups.
  • A signed BAA with your hosting provider and any other vendor that touches PHI. Confirm this with the provider before you store real patient data.

Step 1 - Creating individual accounts and a PHI access group

HIPAA requires unique user identification (164.312(a)(2)(i)): every person who accesses PHI must have their own account, and shared logins are not allowed. Create one account per person, with their full name in the comment field so audit logs are traceable. Replace alice and the name with real values:

sudo adduser --gecos "Alice Smith" alice

Create a group that will own the PHI directory and add only the users who need access:

sudo groupadd phi-access
sudo usermod -aG phi-access alice

Verify the membership:

id alice
uid=1001(alice) gid=1001(alice) groups=1001(alice),1002(phi-access)

When someone leaves, lock the account immediately instead of deleting it, so their audit history stays tied to a valid user:

sudo usermod -L -e 1 alice

Step 2 - Enforcing password quality and account lockout

Even with SSH keys, users need passwords for sudo and console access. Install the password quality module:

sudo apt update
sudo apt install libpam-pwquality

Open the configuration file:

sudo nano /etc/security/pwquality.conf

Set a minimum length and require several character classes:

minlen = 14
minclass = 3
maxrepeat = 3
dictcheck = 1
enforce_for_root

These rules apply the next time a user runs passwd. To lock accounts after repeated failures, configure pam_faillock. First edit its settings file:

sudo nano /etc/security/faillock.conf

Uncomment and set these values to lock an account for 30 minutes after 5 failures:

deny = 5
unlock_time = 1800
audit

Ubuntu does not enable pam_faillock by default, so you must add it to the PAM stack.

Open the authentication stack:

sudo nano /etc/pam.d/common-auth

Replace the pam_unix.so line and the lines after it (up to the end of the primary block) so the block reads exactly:

auth    requisite                       pam_faillock.so preauth
auth    [success=1 default=ignore]      pam_unix.so nullok
auth    [default=die]                   pam_faillock.so authfail
auth    sufficient                      pam_faillock.so authsucc
auth    requisite                       pam_deny.so
auth    required                        pam_permit.so

Then add the account phase:

sudo nano /etc/pam.d/common-account

Add this line at the end of the file:

account required                        pam_faillock.so

From a new terminal, run sudo -k; sudo true with a wrong password a couple of times, then check the counter:

sudo faillock --user your_user
your_user:
When                Type  Source                                           Valid
2026-09-25 10:12:03 TTY   /dev/pts/1                                           V

Reset it after the test with sudo faillock --user your_user --reset.

Step 3 - Hardening SSH and configuring automatic logoff

HIPAA requires automatic logoff after a period of inactivity (164.312(a)(2)(iii)). Set a 15 minute idle timeout for interactive shells:

sudo nano /etc/profile.d/99-tmout.sh
TMOUT=900
readonly TMOUT
export TMOUT

Next, create an SSH drop-in file. On Ubuntu 24.04 the first value found wins, and files are read in alphabetical order, so a 10- prefix takes precedence over 50-cloud-init.conf:

sudo nano /etc/ssh/sshd_config.d/10-hipaa.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSE
Banner /etc/issue.net

LogLevel VERBOSE records the fingerprint of the key used for each login, which links sessions to a specific person. Write a login banner that warns users the system is monitored:

sudo nano /etc/issue.net
Authorized use only. This system processes protected health information.
All activity is logged and monitored. Unauthorized access is prohibited.

Validate the configuration and reload SSH:

sudo sshd -t
sudo systemctl reload ssh

sshd -t prints nothing when the file is valid. Confirm the effective values:

sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|clientaliveinterval'
permitrootlogin no
passwordauthentication no
clientaliveinterval 300

Step 4 - Encrypting the PHI volume with LUKS

Encryption at rest (164.312(a)(2)(iv)) makes PHI unreadable if a disk or snapshot leaves your control. Install cryptsetup and format the dedicated data disk.

sudo apt install cryptsetup
sudo cryptsetup luksFormat --type luks2 /dev/vdb

Type YES in uppercase and enter a strong passphrase. Open the volume, create a filesystem and mount it:

sudo cryptsetup open /dev/vdb phi_data
sudo mkfs.ext4 /dev/mapper/phi_data
sudo mkdir -p /srv/phi
sudo mount /dev/mapper/phi_data /srv/phi

Give the directory to the phi-access group. The setgid bit (2) makes new files inherit the group, and the final 0 removes access for everyone else:

sudo chown root:phi-access /srv/phi
sudo chmod 2770 /srv/phi

Verify that the mount sits on an encrypted mapping:

lsblk -f /dev/vdb
NAME         FSTYPE      FSVER LABEL UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
vdb          crypto_LUKS 2           6b1f0c4e-...
└─phi_data   ext4        1.0         0e8d3a91-...                           18.5G     0% /srv/phi

The passphrase is not stored on the server, so after a reboot an administrator unlocks the volume manually with the cryptsetup open and mount commands above. This is deliberate: a copied disk image is useless without the passphrase. Back up the LUKS header to a secure location outside the server, because a damaged header makes the data unrecoverable:

sudo cryptsetup luksHeaderBackup /dev/vdb --header-backup-file /root/phi_data-luks-header.img

Move that file off the server (for example into your password manager or offline storage) and delete the local copy.

Step 5 - Enabling audit controls with auditd

The audit controls standard (164.312(b)) requires recording and examining activity on systems that contain PHI. Install the Linux audit daemon:

sudo apt install auditd audispd-plugins

Create a rules file:

sudo nano /etc/audit/rules.d/hipaa.rules
## Access to PHI
-w /srv/phi -p rwxa -k phi_access

## 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 sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/sshd_config.d/ -p wa -k sshd_config

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

## File deletions and renames by users
-a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F auid>=1000 -F auid!=unset -k delete

Load the rules and confirm they are active:

sudo augenrules --load
sudo auditctl -l | head -n 5
-w /srv/phi -p rwxa -k phi_access
-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 sudoers

Create a test file as alice and search for the event. The -i flag translates user IDs into names:

sudo ausearch -k phi_access -i --start today | tail -n 5

Audit logs are only useful if they survive. Open the daemon configuration:

sudo nano /etc/audit/auditd.conf

Set rotation and make sure a full disk raises an alert instead of silently dropping events:

max_log_file = 100
num_logs = 20
max_log_file_action = ROTATE
space_left_action = SYSLOG
admin_space_left_action = SUSPEND

Restart the service (auditd refuses a plain restart from systemctl, so use service):

sudo service auditd restart

For tamper resistance, send the events to a central log server. Enable the bundled syslog plugin by setting active = yes in /etc/audit/plugins.d/syslog.conf, then forward syslog to your collector over TLS.

Step 6 - Encrypting data in transit

Transmission security (164.312(e)) means PHI must never cross a network in clear text. If the server exposes a web application through Nginx, allow only TLS 1.2 and 1.3 in the server block that listens on port 443:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

Test and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx

Verify from any machine that TLS 1.1 is rejected. Replace your_domain with your hostname:

openssl s_client -connect your_domain:443 -tls1_1 < /dev/null

The handshake must fail with an error such as no protocols available or alert protocol version. Also make sure databases and internal services listen only on 127.0.0.1 or a private network, and use SSH or TLS for any file transfer.

Step 7 - Creating encrypted, tested backups with restic

HIPAA requires a data backup plan and a disaster recovery plan (164.308(a)(7)). restic encrypts every backup with AES-256 before it leaves the server. Install it and create a password file readable only by root:

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

Store a copy of this password in your password manager. Without it the backups cannot be restored. Initialize a repository on the backup host over SFTP, replacing backupuser and backup_host:

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

Root must be able to SSH to the backup host with a key for this to work. Create a systemd service that runs the backup and applies a retention policy:

sudo nano /etc/systemd/system/restic-phi.service
[Unit]
Description=Encrypted backup of PHI data
RequiresMountsFor=/srv/phi

[Service]
Type=oneshot
Environment=RESTIC_REPOSITORY=sftp:backupuser@backup_host:/srv/restic/phi
Environment=RESTIC_PASSWORD_FILE=/etc/restic/password
ExecStart=/usr/bin/restic backup /srv/phi --tag phi
ExecStart=/usr/bin/restic forget --tag phi --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
ExecStart=/usr/bin/restic check

Add a timer to run it every night at 01:00:

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

[Timer]
OnCalendar=*-*-* 01:00:00
Persistent=true

[Install]
WantedBy=timers.target

Enable the timer and run one backup immediately:

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

A successful run ends with no errors were found from restic check. A backup plan is only valid if it has been tested, so restore the latest snapshot to a scratch directory and compare it:

sudo restic -r sftp:backupuser@backup_host:/srv/restic/phi --password-file /etc/restic/password restore latest --target /tmp/restore-test
sudo diff -r /srv/phi /tmp/restore-test/srv/phi && echo "restore OK"
sudo rm -rf /tmp/restore-test

Record the date and result of each restore test; auditors will ask for it.

Step 8 - Keeping the system patched and collecting evidence

Unpatched software is the most common finding in a HIPAA risk analysis. Ubuntu 24.04 ships unattended-upgrades; make sure it is installed and enabled for security updates:

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

Finally, generate a snapshot of the controls you configured so you can attach it to your risk assessment:

sudo aureport --summary --start this-month
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|clientalive'
getent group phi-access
sudo systemctl list-timers restic-phi.timer

HIPAA requires you to keep security documentation for six years, so store these reports with your policies.

Troubleshooting

auditd rules are not loaded after a reboot. Run sudo augenrules --check and sudo augenrules --load. A syntax error in any file under /etc/audit/rules.d/ stops the whole set from loading; sudo auditctl -l shows what is actually active.

A user is locked out. Check the failures with sudo faillock --user alice and clear them with sudo faillock --user alice --reset.

/srv/phi is empty after a reboot. The LUKS volume is not unlocked automatically. Run sudo cryptsetup open /dev/vdb phi_data and sudo mount /dev/mapper/phi_data /srv/phi. The backup service will not run while it is unmounted thanks to RequiresMountsFor.

restic fails with ssh: Could not resolve hostname or a host key prompt. Connect once as root with sudo ssh backupuser@backup_host to accept the host key, then rerun the service.

Conclusion

You now have an Ubuntu 24.04 server with unique accounts, lockout and automatic logoff, an encrypted PHI volume, audit logging on every access to PHI, TLS-only transport and encrypted backups that are tested by restore. These controls cover the technical safeguards of the HIPAA Security Rule, but they must be backed by a documented risk analysis and policies. Next, forward audit logs to a central SIEM, schedule quarterly access reviews of the phi-access group, and add file integrity monitoring with AIDE.