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.
ImportantHIPAA compliance is not a server setting. It also requires administrative safeguards (risk analysis, policies, workforce training), physical safeguards and a signed Business Associate Agreement (BAA) with every vendor that stores or processes PHI on your behalf. This guide covers only the technical controls on the server.
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
sudoprivileges and SSH key authentication already working. - A second, empty block device for PHI data (this guide uses
/dev/vdb; check yours withlsblk). - 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.
WarningA mistake in PAM files can lock everyone out. Keep a second root or sudo session open until you have tested a new login.
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.
Warning
luksFormatdestroys all data on the device. Double check the device name withlsblkfirst.
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.
