Documentation

Server Setup Script — Complete Guide

serversetup.sh is an interactive Bash script that hardens a fresh Ubuntu server in a single run. It collects all configuration up front, shows you a summary, asks for confirmation, then applies every change in order. A post-install verification pass checks every step and only reboots if everything passed.

Requirements

  • OS: Ubuntu 22.04 or 24.04 (fresh install recommended)
  • Access: Must be run as root (sudo -i or direct root login)
  • Network: Outbound internet access (needed to download packages and optionally fetch your SSH key)

How to Use

Log into your server as root and run the following three commands:

curl -fsSL https://nanoapps.ca/scripts/server-setup/serversetup.sh -o serversetup.sh
chmod +x serversetup.sh
./serversetup.sh

The script collects all configuration input first, then shows a summary and asks you to confirm before making any changes. Nothing is modified until you type yes at the confirmation prompt.

Important: Keep your current SSH session open throughout the entire run. Only open a second session to verify login as the new user after the script finishes — before closing the original session.

Input Prompts

The script asks for five pieces of information before it does anything. All prompts read from /dev/tty directly so they work correctly even when piped.

  • Username — The name for the new sudo account. Press Enter to use the default (admin).
  • Password — Password for the new account. Entered twice to confirm. Cannot be empty.
  • SSH Key URL — A public URL pointing to your SSH public key file (e.g. a raw GitHub Gist or any HTTPS URL). The script downloads it, checks that it looks like a real SSH key, and installs it. Press Enter to skip and use password authentication instead.
  • Hostname — The server’s hostname. Press Enter to keep the current one.
  • Timezone — A valid tz database name (e.g. America/New_York). Press Enter to use America/Winnipeg.

After all prompts a configuration summary is shown. Type yes to proceed or anything else to abort with no changes made.

System Update & Package Install

What it does: Runs apt-get dist-upgrade to bring all installed packages to their latest versions, then installs four tools the rest of the script depends on:

  • curl — used to fetch your SSH key if you provide one
  • fail2ban — brute-force protection for SSH
  • unattended-upgrades — automatic security patch application
  • ufw — the Uncomplicated Firewall

All apt prompts are suppressed with DEBIAN_FRONTEND=noninteractive and existing config files are kept as-is (--force-confold) so nothing unexpected changes.

Automatic Security Updates

What it does: Configures unattended-upgrades to automatically download and install security patches from the Ubuntu security repository. Non-security updates (feature releases, etc.) are left for you to apply manually.

Key settings applied:

  • Only packages from ${distro_codename}-security are auto-installed
  • Automatic-Reboot is set to false — the server will never reboot automatically due to an update

Config is written to /etc/apt/apt.conf.d/50unattended-upgrades.

Sudo User Creation

What it does: Creates a new user account and adds it to the sudo group so it can run commands as root when needed.

If the username you entered already exists on the system, the script updates its password instead of failing.

The password variables are cleared from memory immediately after the account is set up.

SSH Key Setup

What it does: If you provided a key URL during setup, the script downloads the file, validates that it contains a real SSH public key, and installs it as the authorized key for your new user.

Validation checks performed:

  • URL must return HTTP 200
  • Downloaded file must not be empty
  • File must start with a recognized key type (ssh-rsa, ssh-ed25519, ecdsa-sha2-*, etc.)

If any check fails you are prompted to try a different URL or press Enter to skip. The key is installed to /home/USERNAME/.ssh/authorized_keys with correct ownership and permissions (700 on the directory, 600 on the file).

Tip: A convenient way to host your public key is a secret GitHub Gist containing just the contents of your ~/.ssh/id_ed25519.pub file. Paste the raw Gist URL when prompted.

SSH Hardening

What it does: Locks down the SSH daemon configuration using a drop-in file (/etc/ssh/sshd_config.d/99-hardening.conf) that loads last and wins over any conflicting settings.

Settings applied:

  • PermitRootLogin no — direct root SSH login is disabled
  • PasswordAuthentication — set to no if you provided an SSH key, yes if using password login
  • LoginGraceTime 30 — unauthenticated connections are dropped after 30 seconds
  • MaxAuthTries 3 — only 3 attempts per connection before it is closed
  • AllowUsers — only the user you created can log in over SSH

If a 50-cloud-init.conf drop-in exists (common on cloud VMs), it is overwritten to match the chosen auth mode so it cannot override the hardening settings.

The original /etc/ssh/sshd_config is backed up to /etc/ssh/sshd_config.bak before any changes. The config is validated with sshd -t before restarting the service — if validation fails, the backup is restored automatically.

UFW Firewall

What it does: Enables UFW (Uncomplicated Firewall) with a restrictive default policy and opens only what is needed to keep SSH access.

  • Default incoming: DENY — all inbound traffic is blocked unless explicitly allowed
  • Default outgoing: ALLOW — the server can make outbound connections freely
  • Port 22/tcp: ALLOW — SSH access is preserved

After the script finishes, open additional ports for any services you install (e.g. ufw allow 80/tcp for a web server).

Fail2Ban

What it does: Configures Fail2Ban to watch SSH login attempts and automatically ban IPs that show signs of brute-force behavior.

Settings applied (written to /etc/fail2ban/jail.d/sshd.conf):

  • maxretry: 5 — 5 failed attempts triggers a ban
  • findtime: 600 — the 5 attempts must occur within a 10-minute window
  • bantime: 3600 — banned IPs are blocked for 1 hour
  • backend: systemd — reads SSH logs from the systemd journal

Fail2Ban is enabled and started immediately. Current ban status is visible in the MOTD each time you log in.

Hostname

What it does: Sets the server hostname using hostnamectl set-hostname. The hostname you enter is validated — only letters, numbers, and hyphens are accepted (no leading or trailing hyphens, maximum 63 characters).

Press Enter at the hostname prompt to leave the current hostname unchanged.

Timezone

What it does: Sets the system timezone using timedatectl set-timezone. The value you enter is validated against the system’s tz database before it is applied.

The default is America/Winnipeg. Press Enter to accept it, or enter any valid timezone name (e.g. America/New_York, Europe/London, UTC).

IPv6 Disable

What it does: Disables IPv6 at the kernel level by writing to /etc/sysctl.d/99-disable-ipv6.conf and applying it immediately with sysctl -p.

This reduces the server’s attack surface on environments where IPv6 is not needed or used. The setting persists across reboots.

Login Message (MOTD)

What it does: Replaces Ubuntu’s default login message with a clean, informative status dashboard that displays every time you SSH in.

The MOTD shows:

  • Hostname and current date/time
  • System uptime and load averages (1m / 5m / 15m)
  • Memory usage (used / free / total in GB)
  • Disk usage for the root partition
  • Public and private IP addresses
  • Last login IP and time
  • Number of active sessions
  • Pending security updates (green if up to date, yellow if updates are waiting)
  • Status of fail2ban, ufw, and unattended-upgrades (green if all running, red if any are down)
  • UFW open ports
  • Fail2Ban current ban count and banned IPs

The script is installed to /etc/update-motd.d/99-hardened-motd and all other MOTD scripts are disabled so only this one runs.

Post-Install Verification

What it does: After all configuration is applied, the script runs a series of automated checks to confirm everything worked correctly.

Checks performed:

  • User exists and is in the sudo group
  • SSH authorized_keys installed (if key mode)
  • SSH hardening drop-in config present
  • PermitRootLogin is set to no
  • PasswordAuthentication is set correctly for the chosen mode
  • SSH service is running
  • UFW is active
  • UFW allows port 22
  • Fail2Ban is running
  • Timezone is set to the requested value
  • IPv6 is disabled
  • MOTD script is installed and executable

If all checks pass, the server reboots automatically after a 5-second countdown (press Ctrl+C to cancel). If any check fails, the reboot is cancelled and the specific issues are listed so you can investigate before rebooting manually.

All runs are logged to /var/log/hardening.log.

After the Script

Once the server has rebooted, log in as the new user (not root). A few things to do next depending on what the server will run:

  • Open additional firewall ports — e.g. sudo ufw allow 80/tcp for HTTP, sudo ufw allow 443/tcp for HTTPS
  • Install your software — the server is now at a clean, hardened baseline ready for whatever you are building
  • Check the MOTD — the login dashboard will show any pending updates or service issues on every login going forward
Reminder: Verify you can log in as the new user in a second terminal before closing your original root session. Once root login is disabled, the new user account is your only way back in.

Server Setup Script is developed by NanoApps. For support, visit nanoapps.ca.

↑ Back to top