Post

Ansible Script For Basic Server Security

Basic security on a Debian-based Linux server is not rocket science. Still, over the years I’ve had my fair share of interventions on public servers that completely ignored even the bare minimum of sane security practices — on publicly exposed machines.

Because of that, I decided to share a very small and simple Ansible playbook that sets up a minimal but fairly robust baseline for server hardening.

This playbook applies a few basic, yet effective configurations:

  • Changes the default SSH port to a higher, non-standard value, effectively avoiding most automated scanners
  • Disables password authentication and enforces key-based SSH access only
  • Sets up fail2ban to mitigate brute-force attempts
  • Configures UFW (Uncomplicated Firewall) with only the required SSH port open

Nothing fancy. Just sensible defaults.

Please note: this playbook assumes SSH keys are already configured. Make sure you do it before running it, otherwise you’ll lock yourself out. You can either copy your public key manually, or simply run:

1
ssh-copy-id user@server_address

While I strongly recommend avoiding exposed WAN ports altogether and using a VPN (like WireGuard) whenever possible, even within VPN-only networks, I still apply this baseline. Beyond basic hygiene, this kind of baseline hardening is a good habit to apply consistently across all servers. Feel free to tweak it as you see fit, like changing the ssh port to one of your liking or adding a package update and a reboot at the end.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
---
- name: Basic server security setup
  hosts: setup_servers
  become: true

  vars:
    ssh_port: 54321

  tasks:
    - name: Update APT cache
      apt:
        update_cache: yes

    - name: Install fail2ban
      apt:
        name: fail2ban
        state: present

    - name: Configure fail2ban jail for SSH
      copy:
        dest: /etc/fail2ban/jail.d/ssh.conf
        content: |
          [sshd]
          enabled = true
          port    = 
          logpath = %(sshd_log)s
          backend = systemd
          maxretry = 5
          findtime = 600
          bantime  = 3600

    - name: Ensure fail2ban is enabled and started
      service:
        name: fail2ban
        enabled: true
        state: started

    - name: Configure SSH daemon
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: ""
        line: ""
        state: present
      with_items:
        - { regexp: '^#?Port ', line: "Port " }
        - { regexp: '^#?PermitRootLogin ', line: 'PermitRootLogin no' }
        - { regexp: '^#?PasswordAuthentication ', line: 'PasswordAuthentication no' }

    - name: Allow new SSH port in UFW
      ufw:
        rule: allow
        port: ""
        proto: tcp

    - name: Enable UFW
      ufw:
        state: enabled
        policy: deny
        direction: incoming
        force: yes

    - name: Restart SSH
      service:
        name: ssh
        state: restarted

And of course, don’t forget to configure your inventory file:

1
2
3
4
[setup_servers]
<ip> ansible_user=<user> ansible_port=22

And, to run it:

1
ansible-playbook -i inventory.ini basic_security_setup.yaml

And just like that, basic server security, with almost zero work.

That said, keep in mind this is still basic server security. It’s solid, but the moment you expose additional services to the internet, you’re also opening new attack vectors that you’ll need to actively monitor and harden against.