← Back to list

Setup new Ubuntu machine

Following is the minimum setup i need every time i spin a new VPS on any cloud provider.

Faaiz SHAH · 2025-08-05 22:18 · 0 claps · 4.3 min read paywalled
#ubuntu #zsh #oh-my-zsh #initial-setup #pyenv-virtualenv
Open on Medium ↗
Wiki topics: 🔓 · Open Source

Setup new Ubuntu machine

Following is the minimum setup i need every time i spin a new VPS on any cloud provider.

1. Prepare the machine

# Update base system and install essentials

sudo apt update && sudo apt -y upgrade
sudo apt -y install build-essential curl wget git ca-certificates gnupg lsb-release \
               software-properties-common unzip

sudo apt autoremove -y
sudo apt autoclean
sudo reboot

2. Zsh, Oh‑My‑Zsh, theme & plugins

sudo apt -y install zsh git fonts-powerline  # powerline fonts needed by agnoster
sudo chsh -s /usr/bin/zsh $USER              # log out/in to make zsh default

# Oh‑My‑Zsh unattended
RUNZSH=no KEEP_ZSHRC=yes \
  sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

# Clone extra plugins (they live outside the main repo)
ZSH_CUSTOM="$HOME/.oh-my-zsh/custom"
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM}/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM}/plugins/zsh-syntax-highlighting

# Enable theme + plugins
sed -i 's/^ZSH_THEME=.*/ZSH_THEME="agnoster"/' ~/.zshrc
sed -i 's/^plugins=.*/plugins=(git rake rbenv ruby docker zsh-autosuggestions zsh-syntax-highlighting)/' ~/.zshrc
exec zsh

2.1 Set the new hostname(if you want)

sudo hostnamectl set-hostname your_desired_hostname

Exit the terminal and reload and you will see the changes have been applied

3. Docker Engine + Compose plugin

# Add Docker’s GPG key & repository
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

sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

verify:

docker --version      
docker compose version

4. Pyenv installation

sudo apt update
sudo apt -y install --no-install-recommends \
     make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev \
     libsqlite3-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev \
     curl git ca-certificates

Fetch pyenv & helpers

curl https://pyenv.run | bash      # installs into ~/.pyenv
# (optional) virtualenv plugin so you can do `pyenv virtualenv …`
git clone https://github.com/pyenv/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv

wire in ZSH

export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

Reload

exec zsh

Install the Python version your project needs

# Check repo – suppose it's 3.11
pyenv install 3.11.9
pyenv virtualenv 3.11.9 kg2gv
pyenv activate kg2gv   # your prompt will change

5. SSH Configuration (Port your_desired_port)

Backup original SSH config

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup

Edit SSH configuration

sudo nano /etc/ssh/sshd_config

Add/modify these settings:

# Change default port
Port your_desired_port

# Disable root login
PermitRootLogin no

# Use SSH protocol 2 only
Protocol 2

# Limit authentication attempts
MaxAuthTries 3
MaxSessions 2

# Disable password authentication (use keys only)
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

# Disable empty passwords
PermitEmptyPasswords no

# Disable X11 forwarding if not needed
X11Forwarding no

# Set login grace time
LoginGraceTime 60

# Allow specific users only (replace 'yourusername' with actual username)
AllowUsers yourusername

# Disable unused authentication methods
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no

# Use strong ciphers
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512

Restart SSH service

sudo systemctl restart sshd
sudo systemctl status sshd

You will see something like below

sudo systemctl status sshd
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2025-08-08 17:52:29 UTC; 14ms ago
       Docs: man:sshd(8)
             man:sshd_config(5)
    Process: 1175 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
   Main PID: 1176 (sshd)
      Tasks: 1 (limit: 2251)
     Memory: 1.7M
        CPU: 17ms
     CGroup: /system.slice/ssh.service
             └─1176 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"

Aug 08 17:52:29 your_desired_hostname systemd[1]: Starting OpenBSD Secure Shell server...
Aug 08 17:52:29 your_desired_hostname sshd[1176]: Server listening on 0.0.0.0 port your_desired_port.
Aug 08 17:52:29 your_desired_hostname sshd[1176]: Server listening on :: port your_desired_port.
Aug 08 17:52:29 your_desired_hostname systemd[1]: Started OpenBSD Secure Shell server.

⚠️ IMPORTANT: Before closing your current session, test SSH connection on the new port:

ssh -p your_desired_port yourusername@your_server_ip

6. Firewall Configuration (UFW)

# Reset UFW to defaults
sudo ufw --force reset

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH on custom port
sudo ufw allow your_desired_port/tcp

# Allow HTTP and HTTPS for your web application
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Allow specific ports for your application if needed
# sudo ufw allow 3000/tcp  # Next.js dev server
# sudo ufw allow 5432/tcp  # PostgreSQL (only if external access needed)

# Enable UFW
sudo ufw enable

# Check status
sudo ufw status verbose

7. Additional Security Hardening

Configure Fail2Ban

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Add these configurations:

[DEFAULT]
bantime = 1800
findtime = 600
maxretry = 3
backend = systemd

[sshd]
enabled = true
port = 51022
filter = sshd
logpath = /var/log/auth.log
bantime = 3600
maxretry = 3

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3

[nginx-dos]
enabled = true
filter = nginx-dos
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 600

Restart the service

# Start and enable fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban

8. Generate SSH key

Following is the command

ssh-keygen -t ed25519 -C "OVH-poc-server-2025" -f ~/.ssh/ovh_poc_server

You will see something like

Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ubuntu/.ssh/ovh_poc_server
Your public key has been saved in /home/ubuntu/.ssh/ovh_poc_server.pub
The key fingerprint is:
SHA256:VIeLQpGO4VDOisWyejRoqFylvm3ZHq4MVFq+HdO/lBc OVH-poc-server-2025
The key's randomart image is:
+--[ED25519 256]--+
|    ..           |
|   o.o           |
|  *.+ = .        |
| + B.X = o E     |
|o.o.X.B S . .    |
|oo++o=oo + .     |
|+.+*oo  . o      |
|..+o.    .       |
|.o               |
+----[SHA256]-----+

Now you can see the private key and public key. Here is the public key:

cat .ssh/ovh_poc_server.pub

Output will be like:

ssh-ed25519 AAAAC3NzaC1lZDI1NLvUwkbwobbwdTE5AAAAIDTNd+w4KI6Qg1+gFoAcUxWe8Q5Z6Dz0 OVH-poc-server-2025

Now after adding SSH to GitHub, you will get error if you try to clone the repo

git clone git@github.com:your_user/your_repo.git
Cloning into 'your_repo'...
git@github.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

The issue is that Git is trying to use the default SSH key (~/.ssh/id_rsa or ~/.ssh/id_ed25519), but our key has a custom name (ovh_poc_server). Here is fix to this:

9. Configure SSH to Use Your Custom Key (Recommended)

Create an SSH config file to tell Git which key to use for GitHub:

# Create SSH config file
nano ~/.ssh/config

Add this configuration:

# GitHub configuration
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/ovh_poc_server
    IdentitiesOnly yes

Then set proper permissions:

chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/ovh_poc_server
chmod 644 ~/.ssh/ovh_poc_server.pub

Now try cloning again:

git clone git@github.com:your_user/your_repo.git

메타데이터
post_id
71ab1bacdfd1
slug
setup-new-ubuntu-22-04-machine-71ab1bacdfd1
url
https://medium.com/@faaizhussain/setup-new-ubuntu-22-04-machine-71ab1bacdfd1
canonical_url
https://medium.com/@faaizhussain/setup-new-ubuntu-22-04-machine-71ab1bacdfd1
author_url
https://medium.com/@faaizhussain
status
ok
fetched_at
2026-08-04 23:03:49