Technology

VPS Security: SSH, Firewalls and Recovery

Securing a VPS means managing access, exposed services, updates, application settings and recovery throughout its life. Weak authentication, unnecessary network access and unsupported software can increase the risk of compromise. This guide explains a practical order for improving those controls, with selected Debian 12 examples. The commands are examples for a compatible system, not a script to paste over an existing production configuration.

Identity, SSH, and Least Privilege

Create a separate administrator with the required sudo privileges and verify both SSH key login and privilege escalation before restricting the original account. Keep the current session and an independent provider console available during changes. Disabling direct root SSH access supports individual accountability, but an administrator with unrestricted sudo still has powerful privileges. Changing the SSH port may reduce some automated probes; there is no universal reduction percentage and targeted scanners can still find it.

The following example assumes a fresh Debian 12 server with OpenSSH Server and sudo installed, SSH currently on port 22, and an unused account name admin. Adapt names and addresses before use. First create the account through an existing trusted administrator session or console, then copy and test the key from your workstation. The initial ssh-copy-id step requires an authentication method already available to the new account. If password login is disabled and no key is installed, provision the public key through the trusted administrator session or console instead; do not enable passwords globally just to follow this example. Check the server host-key fingerprint through a trusted channel. Keep the original session open throughout.

In the existing root session on the server:

adduser admin
usermod -aG sudo admin

On your workstation, generate a separate key if you do not already have a suitable one, protect it with a passphrase, and replace SERVER_IP with the server address:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/vps_admin_ed25519 -C "admin@example"
ssh-copy-id -i ~/.ssh/vps_admin_ed25519.pub admin@SERVER_IP
ssh -t -i ~/.ssh/vps_admin_ed25519 admin@SERVER_IP 'sudo id -u'

The remote command should print 0, confirming that the allowed command ran with effective root privileges. Only after key login and this sudo execution test succeed, configure the effective SSH server policy. This baseline uses keys alone, not MFA. Apply these directives in the appropriate SSH configuration file; inspect existing Includes and Match blocks rather than assuming an appended value overrides earlier settings:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
AllowUsers admin

From the established administrator session, check syntax and the effective configuration for your actual client address and hostname. The documentation values below must be replaced. Confirm that the output matches the intended policy and still uses the expected port before reloading Debian’s ssh service:

sudo /usr/sbin/sshd -t
sudo /usr/sbin/sshd -T -C user=admin,addr=198.51.100.10,host=client.example
# After reviewing the effective settings:
sudo /usr/sbin/sshd -t && sudo systemctl reload ssh

Test a new key-authenticated session again before closing the original one. For TOTP-based MFA, configure and test the supported PAM integration and an appropriate method chain such as publickey,keyboard-interactive; it also requires keyboard-interactive authentication to be enabled. Do not claim the key-only example enforces a second factor. SSH certificates can support time-limited access, but are not a second factor by themselves.

Patch Management and Reducing the Attack Surface

Apply security updates according to exploit urgency and service impact. An actively exploited issue may require action sooner than a routine maintenance window. On supported Debian or Ubuntu installations, configure unattended-upgrades and verify its repository coverage, exclusions, logs and reboot policy; adding a repository does not automatically put it under that policy. RPM-based systems have distribution-specific update tooling, so follow the documentation for the actual release. Live kernel patching covers supported kernels and fixes only. It does not replace every update or reboot. Test significant changes on a representative system and prepare a recovery path.

If a kernel update leaves the VPS unable to boot, “Server Kernel Panic: Root-Cause Analysis and Recovery” explains how to collect console evidence and test the previous kernel before considering a reinstall.

Inventory packages, services, socket activation and scheduled jobs before removing anything. A package that looks unnecessary may support monitoring, backups or another application. Preview removal transactions and confirm service dependencies; do not automatically purge a fixed list or stop services because they appear in a generic checklist. Kernel settings also need a routing-aware review: strict reverse-path filtering can disrupt asymmetric routing, VPNs or multi-homed hosts.

Examples of inspection and package-removal previews on Debian 12:

systemctl list-unit-files --state=enabled
systemctl list-sockets --all
sudo apt-get -s purge telnet rsh-server xinetd
sudo apt-get -s autoremove

The -s commands simulate package changes; review the proposed removals before choosing any real removal. Disable only services you have confirmed are unused. The following is an illustrative non-router baseline for a file such as /etc/sysctl.d/99-hardening.conf; check existing and per-interface settings and test application behavior:

net.ipv4.tcp_syncookies=1
net.ipv4.conf.all.accept_source_route=0
net.ipv4.conf.default.accept_source_route=0
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
kernel.kptr_restrict=2
fs.protected_hardlinks=1
fs.protected_symlinks=1

Do not apply rp_filter=1 universally: strict mode can disrupt asymmetric or complex routing. Inspect both all and interface-specific values. Default-interface settings apply when interfaces are created; they do not replace checking existing interfaces. On a non-forwarding IPv4 interface, accepting redirects is enabled if either its own or the all value is true, so both must be false to disable it there. SYN cookies are a backlog-overflow fallback, not additional bandwidth. sysctl --system reapplies all system sysctl configuration files, including /etc/sysctl.conf; inspect filename precedence and conflicting settings rather than treating it as loading only 99-hardening.conf. Apply changes from a session with a recovery route, then inspect effective values, errors and network behavior:

sudo sysctl --system

Firewalls, Rate Limiting, and DDoS Readiness

Use the distribution-supported firewall manager that your team can maintain. UFW can simplify host rules; nftables supports more detailed policies. Avoid independently managing overlapping rule sets through multiple tools. Permit the working management path before enabling a deny-by-default policy, and check IPv4 and IPv6. Containers, virtual networks and provider firewalls may add other packet paths that require separate checks. Fail2ban can ban sources after matching failures in logs; it is not a universal rate limiter or a complete defense against distributed attempts.

When an attack saturates the VPS network connection, host filtering cannot recover the missing upstream capacity. Select provider filtering or a scrubbing service for the actual protocols, addresses and attack capacity involved. An HTTP reverse proxy does not automatically protect every TCP or UDP service, and ordinary DNS hosting does not scrub traffic. Prevent unintended direct origin access where a proxy is part of the design. See the DDoS protection guide and the CDN guide for those separate roles. The baseline below assumes UFW and Fail2ban are installed on Debian 12, no competing firewall manager, and SSH still on TCP 22. The SSH rule initially permits all source addresses; narrow it to your confirmed management IP or VPN range after testing access. Inspect provider ACLs, existing UFW rules and the IPv6 setting before enabling it.

sudo ufw status verbose
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status verbose

Only open web ports if the server needs them. HTTP/3, a VPN and other applications may need additional protocol-specific rules. If changing SSH ports, keep the old working path until the new listener and firewall path are tested. This Fail2ban example requires the distribution’s Python systemd bindings and an accessible system journal. The systemd backend uses the SSH filter’s journalmatch, not a logpath; verify that it matches this server’s actual SSH events. Place the illustrative override in /etc/fail2ban/jail.d/sshd.local. Its port value is passed to the ban action and does not change the SSH listening port:

[sshd]
enabled = true
backend = systemd
port = 22
filter = sshd
maxretry = 4
bantime = 1h
findtime = 10m

These thresholds are examples. Verify that the jail reads the intended logs and does not ban legitimate administration. Validate before restarting, then inspect the active jail:

sudo fail2ban-client -t && sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Encrypt Everywhere: TLS, VPN, and Data at Rest

Use TLS for browser and service connections, verify certificates and automate monitored renewal. TLS protects transport; it does not fix a compromised endpoint or application. Short-lived certificates reduce the exposure period of an unrenewed credential but do not make revocation instantaneous. Before enabling HSTS, establish reliable HTTPS for the hostname, then begin with a short lifetime. Include subdomains or submit for preload only after assessing every affected service and the difficulty of undoing that choice. The SSL/TLS guide explains the certificate layer. Restrict management to a trusted path where appropriate. Installing a VPN alone does not restrict SSH exposure: configure listening addresses or firewall rules to permit management only through the intended VPN interface or tunnel addresses, and test the independent recovery console. Protect the VPN endpoint and its keys; it does not replace SSH/TLS authentication or device security. See the VPN guide for that scope.

At-rest encryption needs a clear threat model. LUKS or provider-managed volume encryption can protect offline data, but does not automatically stop privileged access to a mounted filesystem or a running guest. Keep application secrets out of repositories, public web paths and logs. A protected .env file can be part of a design if access is restricted; a secrets manager or KMS improves access and lifecycle controls rather than making a compromised application unable to read its own credentials. Choose database TLS and supported storage encryption as needed, with a tested key-recovery plan. The following directives belong inside an existing Nginx HTTPS server configuration with valid certificate settings:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=300" always;

ssl_ciphers configures the older OpenSSL cipher-list interface, including TLS 1.2; TLS 1.3 suite selection uses a separate interface. This example leaves the TLS 1.3 library defaults in place. Check your Nginx/OpenSSL version and required client compatibility. The short HSTS lifetime is a staged example, not a universal final setting. Test configuration before reload and verify an actual HTTPS response:

sudo nginx -t && sudo systemctl reload nginx

Monitoring, Logging, and Intrusion Detection

Centralize useful system and application logs in a separately protected collector, with access restrictions and an appropriate retention policy. auditd can record configured security events; AIDE can compare files against a trusted baseline. Neither these tools nor a rootkit scanner establishes that a host is free from compromise. Protect the baseline and investigate changes before updating it. Monitor CPU pressure, remaining disk space and its rate of consumption, application errors and unusual authentication activity. Choose thresholds from normal workload behavior and the time needed to respond, rather than copying a universal percentage.

Heartbeat checks and service-level dashboards can reveal some failures early, but they do not guarantee advance warning of every outage. The website uptime guide explains availability and service objectives. Route actionable alerts to an accountable person and exercise the response plan at a frequency justified by risk and change. Synchronize system clocks and restrict access to logs. A local immutable file flag is not a guarantee against an attacker with sufficient administrative privileges; independently protected remote or retention-locked storage can reduce that shared risk.

On Debian 12, review the package installation, then enable audit collection:

sudo apt install auditd
sudo systemctl enable --now auditd

An illustrative persistent file watch belongs in a file such as /etc/audit/rules.d/50-account-watch.rules:

-w /etc/passwd -p wa -k passwd_changes

Load and inspect the rules through the distribution tooling:

sudo augenrules --load
sudo auditctl -l

This legacy watch syntax illustrates one account-file event source, not a complete audit policy. Review existing rules, performance and retention; an immutable audit policy may require its planned reboot workflow before changes take effect. For AIDE, initialize only after establishing a trusted system state:

sudo apt install aide
sudo aideinit

Read the prompts and configured database paths before accepting a new baseline. Do not blindly move a guessed output file over an existing trusted database. Configure periodic checks and delivery of their results using the installed package’s documented mechanism, then test that a representative change is reported.

Web Stack and Application Hardening

Separate application services with dedicated Unix users and minimal filesystem permissions. systemd options such as ProtectSystem, PrivateTmp and NoNewPrivileges can further restrict a compatible service, but must be tested against its required behavior. Browser security headers, a tuned WAF such as ModSecurity with OWASP CRS, and endpoint rate limits provide different controls. Tune them with representative traffic and monitor false positives; no generic rule set guarantees that all legitimate users will be unaffected.

PHP-FPM application workers should run with a suitable unprivileged pool identity. Restrict upload permissions, store uploaded files where they cannot execute, validate content and enforce authorization on downloads. Read-only deployments and minimal containers can simplify controlled releases, but their credentials, writable paths and network exposure still need review. Published Docker ports can follow packet paths that bypass ordinary UFW host rules; inspect the Docker-supported firewall controls and test exposure separately. A CDN can reduce origin load for eligible traffic, but cacheability, invalidation and origin access need deliberate settings. See CDN advantages for your website. The following Nginx headers are a starting point for application-specific testing:

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data:; object-src 'none'" always;

The CSP is report-only: it does not enforce these restrictions, and this example does not configure a remote report destination. Inspect browser diagnostics and legitimate scripts, images, fonts and frames before designing and enforcing the policy. SAMEORIGIN can itself block intended third-party framing. With standard Nginx inheritance, a lower-level configuration containing its own add_header directives does not inherit the parent’s list. Keep all required headers together at the effective level, including the separately discussed HSTS header on HTTPS, and inspect responses from representative locations and error paths. Test the configuration before reload.

Backups, Snapshots, and Recovery Planning

Define RPO as the acceptable data-loss time window and RTO as the target time to restore service. Choose backup frequency, retention and recovery capacity from those business needs and measured restores. The 3-2-1 approach is a useful starting point, but copies sharing an account, credentials or destructive automation can still fail together. Maintain independently protected copies, with offline or retention-locked storage where appropriate. Encrypt sensitive backups and separate key access while keeping a tested recovery route. Provider snapshots are useful recovery inputs; they do not automatically establish application consistency or an independent backup.

An hourly, daily and weekly retention schedule can be an example, not a universal recommendation. Combine infrastructure recovery with database-appropriate backups. Select logical or physical tools supported by the exact MySQL or MariaDB release; do not assume Percona XtraBackup supports every MariaDB version. PostgreSQL point-in-time recovery requires a usable base backup and the necessary complete WAL sequence, with a tested restore configuration. Checksums can detect some corruption but cannot replace restore and application-consistency tests. Protect the provider control panel with suitable MFA and least-privilege access, and revoke or renew credentials when exposure or access changes warrant it. Network recovery planning also depends on protocol-appropriate DDoS protection and working TLS certificate practices.

Bringing It All Together

Begin with a separate administrator, verified key login and sudo access, and a working recovery console. Then apply network restrictions and updates in tested steps. Add application controls, useful monitoring and a measured restore from an independent backup. Document the expected result, verification method and rollback owner for each change. Repeat those checks as the workload evolves; a maintained and tested configuration is more useful than a one-time list of installed security tools.

Frequently Asked Questions

Create a separate administrator and verify SSH key login and sudo in a new session before disabling the original access method. Keep a provider console and the current session available. Permit the working management path before enabling a restrictive firewall, apply relevant updates and test service behavior. Then document the configuration, monitoring and recovery process.

It may reduce some automated probes, but targeted scanners can still find the service and there is no universal reduction percentage. Prioritize verified key authentication, appropriate MFA, source restrictions and updates. If changing ports, test the new listener and firewall path before closing the working one. A key-only AuthenticationMethods setting does not enforce MFA.

Base timing on exploit urgency, affected components and service impact. Verify automatic updates cover the intended repositories and report failures. Reboot when required to activate relevant fixes using a planned recovery path; do not delay an urgent fix merely to reach a monthly schedule. Live kernel patching covers supported kernels and fixes only and does not eliminate all updates or reboots.

Match protection to the exposed protocols and the provider service scope. An HTTP CDN, WAF and request limits can help with eligible web traffic; they do not automatically protect all TCP or UDP services. Volumetric attacks may require upstream filtering before the VPS connection is saturated. Restrict unintended direct origin access and verify monitoring, capacity and the response procedure.
↑