A concise, step-by-step guide for securing and configuring a fresh Ubuntu 24.04 server.
Alternatively you can skip the text and use the cloud-init script files cloud-init.yaml
, cloud-init-docker.yaml
, cloud-init-lemp.yaml
, or cloud-init-nginx-proxy.yml
for an automated setup.
- Deploy the official Ubuntu 24.04 LTS image from your cloud provider or ISO.
- Log in as the default user (or via SSH key if provided).
Ensure your server's timezone is set correctly to avoid issues with logs and scheduled tasks. Adjust the timezone as needed.
sudo timedatectl set-timezone Europe/Amsterdam
Install all required packages in one go:
sudo apt update && sudo apt upgrade -y
sudo apt install -y ufw rsyslog qemu-guest-agent fail2ban unattended-upgrades
ufw
: Uncomplicated Firewall for basic firewall managementrsyslog
: Reliable system and kernel loggingqemu-guest-agent
: Enhanced VM integration (for cloud/VM environments)fail2ban
: Protects against brute-force attacksunattended-upgrades
: Enables automatic security updates
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable
sudo ufw status verbose
Create or edit /etc/sysctl.d/99-custom.conf
:
# IP Spoofing protection (IPv4)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP broadcast requests (IPv4)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Disable source packet routing (IPv4)
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Enable TCP SYN cookies (IPv4)
net.ipv4.tcp_syncookies = 1
# Log Martians (IPv4)
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Disable source packet routing (IPv6)
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# Disable IPv6 redirects
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
# Disable IPv6 router advertisements if not needed
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
Apply changes:
sudo sysctl --system
sudo dpkg-reconfigure --priority=low unattended-upgrades
List running services:
systemctl list-units --type=service --state=running
Disable unnecessary ones, e.g.:
sudo systemctl disable --now avahi-daemon
sudo systemctl disable --now cups
sudo systemctl disable --now ModemManager
sudo systemctl disable --now multipathd
sudo systemctl disable --now multipathd.socket
sudo systemctl disable --now udisks2
sudo systemctl enable --now fail2ban
Create /etc/fail2ban/jail.local
:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 5
Restart Fail2Ban:
sudo systemctl restart fail2ban
The following steps will install Docker Engine and Docker Compose, configure basic security, and verify the installation.
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
Add your user to the docker
group to run Docker without sudo
:
sudo usermod -aG docker $USER
# Log out and back in for group changes to take effect
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- Only trusted users should be added to the
docker
group. - Consider enabling the Docker daemon's rootless mode for extra isolation (see Docker docs).
- Use UFW to restrict access to Docker-exposed ports as needed.
docker --version
docker compose version
sudo docker run --rm hello-world
If you see a "Hello from Docker!" message, your installation is working.
The following steps will install and configure a secure LEMP stack (Linux, Nginx, MariaDB, PHP-FPM 8.3) suitable for hosting PHP applications. MariaDB is used for its performance and open-source focus, but you can substitute MySQL if preferred.
sudo apt update
sudo apt install -y nginx mariadb-server php8.3-fpm php8.3-mysql php8.3-cli php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip php8.3-bcmath php8.3-intl php8.3-soap php8.3-redis php8.3-imagick
Run the secure installation script and follow the prompts. Use a strong root password and answer the security questions as appropriate for your environment.
sudo mysql_secure_installation
Note: Do not use the example passwords below in production. Replace all placeholder values with strong, unique passwords.
sudo mysql -u root -p
Inside the MariaDB shell:
CREATE DATABASE exampledb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'exampleuser'@'localhost' IDENTIFIED BY 'REPLACE_WITH_STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON exampledb.* TO 'exampleuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
PHP-FPM is installed and enabled by default. You may want to adjust settings in /etc/php/8.3/fpm/php.ini
for production (e.g., memory_limit
, upload_max_filesize
, post_max_size
, date.timezone
).
Create a new site configuration file, e.g. /etc/nginx/sites-available/example.com
:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html index.htm;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~* /\.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
access_log off;
}
location ~ /\.ht {
deny all;
}
}
Enable the site and reload Nginx:
sudo mkdir -p /var/www/example.com/public
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Create a test file:
echo '<?php phpinfo();' | sudo tee /var/www/example.com/public/info.php
Visit http://example.com/info.php
to verify PHP is working, then remove the file:
sudo rm /var/www/example.com/public/info.php
Security Note:
- Always use strong, unique passwords for database users.
- Adjust firewall rules to restrict access to only necessary ports (e.g., 80, 443).
- Remove or secure default files and configurations before going live.