Seeskou logo

Seeskou

Automating VPS security with Ansible

August 2, 2025 8 min read Seeskou
ServerSecurityAnsible
Automating VPS security with Ansible

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:

  1. Create a non-root user with sudo privileges.
  2. Set up SSH key authentication for secure access.
  3. Disable root login and password authentication.
  4. Configure the firewall to allow only necessary traffic.
  5. Install and configure Fail2Ban to protect against brute-force attacks.
  6. 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.

  1. 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) }}"
  1. 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'
  1. [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
  1. Restart the SSH service to apply the changes:
    - name: Restart SSH service
      service:
        name: ssh
        state: restarted
  1. 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
  1. 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
  1. 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";
  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! 🔒

Published on August 2, 2025