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.