This is a generic Linux operations baseline, not a Laravel deployment guide. A maintainable server has named owners, non-root access, limited exposure, patch policy, recoverable backups and evidence that each control works.
Establish access before hardening
Create a named sudo user, install an Ed25519 public key, verify a second terminal can log in, then disable root and password login. Keep an out-of-band console path before changing SSH. Allow only required ports through UFW and verify the active rules from another host.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Fail2ban is a response layer, not a substitute for patched software and restricted exposure. Confirm its jail matches your SSH log source and test a safe failure rather than assuming installation equals protection.
systemd owns long-running processes
Do not rely on nohup, tmux or an SSH session for a service. A unit states its user, working directory, restart policy and environment boundary.
[Unit]
Description=Example worker
After=network.target
[Service]
User=deploy
WorkingDirectory=/srv/example
ExecStart=/usr/local/bin/example-worker
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Use an environment file and service hardening rather than embedding secrets in a
unit. systemctl daemon-reload, enable --now and a deliberate restart are
part of the release procedure, not optional typing.
[Service]
User=deploy
WorkingDirectory=/srv/example
EnvironmentFile=/etc/example/worker.env
ExecStart=/usr/local/bin/example-worker
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
Make SSH and logs recoverable
Create the sudo user and its Ed25519 key, then test a second terminal before
disabling root or password access. Keep provider console access available. In
/etc/ssh/sshd_config, use PermitRootLogin no, PasswordAuthentication no
and AllowUsers deploy; validate with sshd -t before reload. Fail2ban then
reads the actual SSH journal or log source—it is not a replacement for keys and
patches.
Persist journals and bound their disk usage so an incident survives a reboot without filling the volume:
# /etc/systemd/journald.conf.d/retention.conf
[Journal]
Storage=persistent
SystemMaxUse=1G
MaxRetentionSec=14day
sudo systemctl restart systemd-journald
journalctl -u example-worker --since '1 hour ago'
sudo fail2ban-client status sshd
Patch, monitor and rehearse recovery
Enable unattended security upgrades when fleet policy permits, alert on a reboot-required marker, and schedule kernel restarts. Baseline monitoring is small but concrete: disk and inode usage, CPU/RAM pressure, service state, certificate expiry, backup age and off-host restore success. A backup is only evidence after an encrypted restore into an isolated environment has completed.
Use systemctl status example-worker for state and journalctl -u example-worker -f for a time-ordered incident view. Set journal retention deliberately so a disk-full event does not erase the evidence needed to diagnose it.
Patch, back up and rehearse recovery
Enable unattended security upgrades where the fleet policy permits, but monitor reboot-required state and schedule kernel reboots. Backups require encrypted off-host storage, retention, alerting and a restore test; a successful backup command is not recovery evidence. Record hostname, service owner, dependencies, secrets rotation, rollback and restore runbook.
When not to use this baseline
Do not apply commands blindly to immutable images, managed Kubernetes nodes or a provider-managed appliance; use the platform's control plane. Do not open ports “temporarily” without an expiry. Laravel-specific PHP-FPM, queues and Nginx settings belong in the deployment guides, where they can be tested together.