1. Introduction
When you spin up a new VPS, it's essentially a fresh Linux installation exposed to the internet with default configurations. This makes it an attractive target for automated attacks, brute force attempts, and malicious actors scanning for vulnerabilities.
Securing your VPS should be your first priority before installing any applications or services. A compromised server can lead to:
- Data theft and privacy breaches
- Resource hijacking for cryptocurrency mining or botnets
- Your server being used to attack other systems
- Complete loss of your data and applications
In this guide, we'll walk through essential security measures that every VPS owner should implement, and we will automate the deployment for new VPS via an Ansible playbook.
2. Prerequisites
Before we begin, you'll need:
- Fresh VPS with root access
- SSH client (Terminal on Linux/macOS, PuTTY on Windows)
- Basic Linux knowledge (understanding of command line)
- Local machine to generate SSH keys
Recommended OS: Ubuntu 22.04+ or Debian 11+. This guide uses Ubuntu 24.04 LTS.
3. Quick summary of steps to take
Securing your VPS involves several key steps:
- Create a non-root user with sudo privileges.
- Set up SSH key authentication for secure access.
- Disable root login and password authentication.
- Configure the firewall to allow only necessary traffic.
- Install and configure Fail2Ban to protect against brute-force attacks.
- Set up automatic security updates for your server.
Now that we have an overview, let's dive into each step in detail and automate the process with Ansible.
4. Creating the Ansible playbook
To automate the security setup, we will create an Ansible playbook that performs all the necessary steps.
But before we do that, we need to prepare our development environment.
4.1 Install Ansible
If you don't have Ansible installed, you can do so with the following command:
sudo apt update
sudo apt install ansible -y
4.2 Create the hosts file
Create a file named hosts.ini
in your project directory to define the target VPS:
[vps]
vps1 ansible_host=YOUR_VPS1_IP
vps2 ansible_host=YOUR_VPS2_IP
[vps:vars]
ansible_user=root
Replace YOUR_VPS1_IP
with the actual IP address of your VPS.
We will use this file to specify which VPS we want to run the playbook on.
4.3 Create the Ansible playbook
Create a file named vps-init-security.yml
in your project directory with the following content:
---
- name: Preload SSH host keys for new VPS
hosts: localhost
gather_facts: no
tasks:
- name: Collect host keys from all VPS hosts
ansible.builtin.shell: "ssh-keyscan -H {{ hostvars[item].ansible_host }} >> ~/.ssh/known_hosts"
loop: "{{ groups['vps'] }}"
changed_when: false
- name: Secure initial setup of VPS
hosts: vps
become: true
vars:
new_username: NEW_USERNAME # Replace with the desired new user
ssh_pubkey_path: "{{ lookup('env','HOME') + '/.ssh/id_ed25519.pub' }}" # Path to your SSH public key
allow_ip: "YOUR_SECURE_IP_ADDRESS" # [OPTIONAL] - Replace with your secure IP address
The first block of the playbook is responsible for preloading the SSH host keys for the new VPS instances. This is important to avoid any SSH connection issues when the playbook is run against the new VPS hosts.
The second block is where we will define the tasks to secure the VPS. Here, we will create a new user, set up SSH key authentication, disable root login, configure the firewall, and install Fail2Ban.
Let's explain the variables:
new_username
: The username for the new non-root user you want to create.ssh_pubkey_path
: The path to your SSH public key, which will be copied to the new user.allow_ip
: An optional variable to allow SSH access from a specific IP address, useful for secure access.
And now, time to add the tasks to the playbook.
- Create a new user and set up SSH key authentication for it and the root user:
tasks:
- name: Create new sudo user "{{ new_username }}"
user:
name: "{{ new_username }}"
groups: sudo
shell: /bin/bash
create_home: yes
- name: Copy SSH key to new user
authorized_key:
user: "{{ new_username }}"
state: present
key: "{{ lookup('file', ssh_pubkey_path) }}"
- name: Copy SSH key to root
authorized_key:
user: root
state: present
key: "{{ lookup('file', ssh_pubkey_path) }}"
- Allow passwordless sudo for the new user and disable password authentication and root login for SSH:
- name: Allow passwordless sudo for user "{{ new_username }}"
copy:
dest: "/etc/sudoers.d/{{ new_username }}"
content: "{{ new_username }} ALL=(ALL) NOPASSWD:ALL\n"
owner: root
group: root
mode: '0440'
- name: Disable root login and password/KbdInteractive authentication in sshd_config
lineinfile:
path: /etc/ssh/sshd_config
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
state: present
create: yes
validate: '/usr/sbin/sshd -T -f %s'
loop:
- { regexp: '^PermitRootLogin', line: 'PermitRootLogin no' }
- { regexp: '^PasswordAuthentication', line: 'PasswordAuthentication no' }
- { regexp: '^KbdInteractiveAuthentication', line: 'KbdInteractiveAuthentication no' }
- name: Disable password authentication in 50-cloud-init.conf
lineinfile:
path: /etc/ssh/sshd_config.d/50-cloud-init.conf
regexp: '^PasswordAuthentication'
line: 'PasswordAuthentication no'
create: yes
validate: '/usr/sbin/sshd -T -f %s'
- [OPTIONAL] - Allow root and password authentication SSH access from a specific IP address:
- name: Add Match block to allow root/password auth for specific IP
blockinfile:
path: /etc/ssh/sshd_config
marker: "# {mark} ANSIBLE MANAGED MATCH BLOCK"
block: |
Match Address {{ allow_ip }}
PasswordAuthentication yes
PermitRootLogin yes
KbdInteractiveAuthentication yes
validate: '/usr/sbin/sshd -T -f %s'
when:
- allow_ip is defined
- allow_ip | length > 0
- Restart the SSH service to apply the changes:
- name: Restart SSH service
service:
name: ssh
state: restarted
- Configure the firewall to allow only necessary ports and enable it:
- name: Install UFW
apt:
name: ufw
state: present
- name: Set UFW default policy (deny incoming, allow outgoing)
ufw:
direction: incoming
policy: deny
- name: Set UFW default policy for outgoing
ufw:
direction: outgoing
policy: allow
- name: Allow SSH port through UFW
ufw:
rule: allow
port: 22
proto: tcp
- name: Enable UFW
ufw:
state: enabled
- Install and configure Fail2Ban to protect against brute-force attacks:
- name: Install fail2ban
apt:
name: fail2ban
state: present
- name: Ensure fail2ban is enabled and running
service:
name: fail2ban
enabled: yes
state: started
# [OPTIONAL] - Configure fail2ban to whitelist the configured secure IP
- name: Add IP whitelist to fail2ban
blockinfile:
path: /etc/fail2ban/jail.local
create: yes
block: |
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 {{ allow_ip }}
when:
- allow_ip is defined
- allow_ip | length > 0
- name: Restart fail2ban
service:
name: fail2ban
state: restarted
- Set up automatic security updates to keep the system secure:
- name: Install unattended-upgrades package
apt:
name: unattended-upgrades
state: present
update_cache: yes
- name: Enable unattended-upgrades
debconf:
name: unattended-upgrades
question: unattended-upgrades/enable_auto_updates
vtype: boolean
value: 'true'
- name: Configure unattended-upgrades for security updates
copy:
dest: /etc/apt/apt.conf.d/50unattended-upgrades
content: |
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
- name: Ensure periodic updates are enabled
copy:
dest: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
- Reboot the VPS and wait for them to come back online:
- name: Reboot the VPS
reboot:
msg: "Reboot initiated by Ansible for security setup completion"
connect_timeout: 5
reboot_timeout: 300
pre_reboot_delay: 0
post_reboot_delay: 30
test_command: whoami
And we are done! You can now run the playbook with the following command:
ansible-playbook -i hosts.ini vps-init-security.yml --ask-pass
This will prompt you for the root password of your VPS, and then it will execute the playbook to secure your VPS.
5. Conclusion
Securing your VPS is an ongoing process, not a one-time setup. The Ansible playbook provided in this guide automates many security hardening tasks, but it's essential to stay informed about best practices and emerging threats.
Key takeaways:
- Never skip system updates
- Always use SSH keys instead of passwords
- Implement proper firewall rules
- Automate security updates where possible
Remember: A compromised server can cost you far more than the time invested in proper security measures.
What's next? Now that your VPS is secured, you can safely proceed with installing your applications, whether it's a web server, database, or mail server.
Stay secure! 🔒