Tula
BlogRatesAboutSecurityPrivacyTerms
Tula
BlogRatesAboutSecurityPrivacyTerms
← Back to blog
Product2 September 2026·9 min read

How we handle a new VPS

When you purchase a new VPS, it is tempting to think of it as an empty server waiting for you to deploy your application.

How we handle a new VPS

The internet sees it differently.

Recently, while setting up infrastructure for Tula, we purchased VPS hosting from HostAfrica. The servers were fresh, had barely been provisioned, and were not yet carrying meaningful production traffic.

Within hours, they were already receiving automated SSH login attempts.

On one server, we discovered thousands of failed password attempts had accumulated in roughly a day before we completed our hardening process. On another, Fail2ban began blocking hostile IP addresses only minutes after being enabled.

Nothing about these servers had been advertised publicly.

They simply existed on the internet.

That experience changed the way we think about provisioning servers. A VPS should not be deployed first and secured later. Hardening is part of provisioning.

This is the process we now follow whenever we bring a new Ubuntu VPS online.

Note: This article describes our experience and the general hardening process we use. It intentionally excludes our IP addresses, usernames, domains, network topology, and other infrastructure-specific information.

Assume the server is being attacked already

The first lesson is the most important.

Do not wait until your application launches before thinking about server security.

Automated systems continuously scan public IPv4 addresses looking for SSH servers, databases, web panels, Docker APIs, outdated software, and common configuration mistakes.

A newly allocated IP address does not receive a grace period.

Our experience was a good reminder that the useful question is not:

“Why would anyone attack our server?”

It is:

“What happens when an automated scanner finds it?”

Your initial configuration should be designed with that assumption.

The hardening sequence we now use

For a typical Ubuntu VPS, our baseline process is:

  1. Create a non-root administrative user and prove that SSH key authentication and sudo work from a completely new connection before changing root access.

  2. Disable SSH password authentication, interactive authentication, and direct root login.

  3. Verify the effective SSH configuration rather than assuming the configuration file you edited is the configuration SSH is actually using.

  4. Install and enable Fail2ban to slow repeated authentication attacks.

  5. Audit every listening port and the actual firewall state before rebuilding a minimal firewall policy.

  6. Install system updates and reboot immediately when required while the server is still new and easy to take offline.

  7. Re-test SSH access, firewall exposure, running services, and application connectivity after the reboot.

The order matters. In particular, never disable your existing method of accessing the server until you have proven that the replacement works.

When possible, we also keep the hosting provider's web console available during this process in case an SSH configuration mistake locks us out.

Step 1: Stop using root as your normal SSH account

A server may initially arrive with root access or another default administrative account.

We create a dedicated administrative user and authenticate using an SSH key.

For example:

sudo adduser deploy
sudo usermod -aG sudo deploy

Then install your public SSH key:

sudo mkdir -p /home/deploy/.ssh
sudo nano /home/deploy/.ssh/authorized_keys

sudo chmod 700 /home/deploy/.ssh
sudo chmod 600 /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy/.ssh

Before doing anything else, open a new terminal and verify:

ssh deploy@your-server-ip

Then:

sudo whoami

You should get:

root

Do not simply test this from your existing SSH session.

That existing session proves only that you have not disconnected yourself yet.

A fresh connection proves that you can get back in.

Step 2: Lock down SSH

Once key-based administrative access is confirmed, we disable the authentication methods we do not need.

We use an SSH configuration drop-in such as:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
X11Forwarding no
MaxAuthTries 3

This removes one of the largest attack surfaces on an ordinary internet-facing VPS: remotely guessing usernames and passwords.

After changing SSH configuration, always test it before restarting the service:

sudo sshd -t

No output generally means the configuration passed validation.

But syntax validation alone is not enough.

We discovered one particularly important issue while hardening our VPS instances.

The configuration file you edited may not be the configuration SSH uses

This was one of the more interesting lessons from our setup.

Ubuntu cloud images can contain additional SSH configuration files under:

/etc/ssh/sshd_config.d/

On multiple servers we provisioned, cloud-init had created its own SSH configuration drop-in.

That meant simply adding:

PasswordAuthentication no

somewhere else did not automatically guarantee that password authentication was disabled.

OpenSSH processes configuration according to its parsing rules, and existing drop-ins can affect the final result.

Instead of trusting what the files appear to say, inspect what SSH believes its configuration actually is:

sudo sshd -T | grep -E \
'permitrootlogin|passwordauthentication|kbdinteractiveauthentication|maxauthtries'

We want the effective result to resemble:

permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
maxauthtries 3

This distinction matters.

There is a major difference between:

“I added a line that disables password authentication.”

and:

“I proved that the running SSH server has password authentication disabled.”

Security depends on the second statement.

Step 3: Try to break your own configuration

After restarting SSH, we test it from fresh connections.

Root SSH should fail.

Password-only authentication should fail.

SSH key authentication for the administrative user should succeed.

sudo should still work.

And we keep the original session open until all of these tests pass.

This sounds overly cautious until the first time you accidentally lock yourself out of a remote server.

Step 4: Add Fail2ban

Disabling password authentication dramatically reduces the value of password guessing, but we still prefer having another layer watching SSH.

On Ubuntu:

sudo apt-get update
sudo apt-get install fail2ban

A simple SSH jail can use settings along these lines:

[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h

Then:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

One of our newly provisioned servers began registering and banning hostile addresses almost immediately after Fail2ban was enabled.

That was a useful visual demonstration of something that is otherwise easy to forget:

An unused-looking server can still be very busy from an attacker's perspective.

Step 5: Never trust a firewall dashboard without checking the machine

This was probably the most surprising issue we encountered.

A server appeared, from the normal UFW status output, to have a relatively restricted firewall configuration.

But when we inspected the actual networking state, additional ports were still reachable because firewall rules had been created elsewhere.

The lesson was simple:

A firewall's management tool and the kernel's actual networking state are not necessarily the same thing.

Before changing firewall rules, we inspect what is listening:

sudo ss -tlnpu

We also inspect the underlying firewall configuration when necessary.

Then we establish a clean policy.

For example:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit 22/tcp

Only services that genuinely need to be reachable from the internet should then be opened.

A web server might require:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Everything else should require a reason.

The goal is not:

“Block the dangerous ports.”

The goal is:

“Expose nothing unless we have deliberately decided it needs to be public.”

That mindset produces much safer infrastructure.

Docker deserves special attention

Docker complicates firewall assumptions.

Publishing a Docker port can cause Docker to manipulate the host's firewall rules, and the resulting service may be reachable in ways that are not obvious from a quick look at UFW.

For example, this:

0.0.0.0:8000:8000

means something very different from:

127.0.0.1:8000:8000

The first potentially exposes the service on every network interface.

The second restricts it to the host itself.

For internal dashboards, databases, monitoring tools, administration interfaces, and application backends that sit behind a reverse proxy, we prefer binding them explicitly to localhost or a private network rather than relying on UFW alone to make them private.

The firewall should be another layer of protection, not the only thing standing between an internal service and the internet.

Step 6: Update immediately

A brand-new VPS can still contain packages that have updates available.

So before considering provisioning complete:

sudo apt-get update
sudo apt-get upgrade

Then check whether Ubuntu requires a reboot:

test -f /var/run/reboot-required && echo "Reboot required"

If it does, this is usually the best possible time to reboot.

There are no users.

There is little or no production traffic.

There should be nothing important to interrupt.

Delaying a required reboot until after the server becomes critical only makes maintenance harder.

Step 7: Reboot — and verify everything again

A security configuration that works before reboot but disappears afterward is not a security configuration.

After rebooting, we repeat the important checks.

Can the administrative account still connect using its SSH key?

Is root SSH still disabled?

Is password authentication still disabled?

Is Fail2ban running?

Is the firewall enabled?

Which ports are actually listening?

Which ports can actually be reached externally?

Are Docker containers exposing anything unexpected?

Is the application still working?

Provisioning is complete only after the post-reboot state has been verified.

One command taught us an important security principle

There is a pattern running through almost every issue we encountered.

We could have looked at our SSH configuration and assumed passwords were disabled.

We could have looked at UFW and assumed certain ports were closed.

We could have looked at a Docker Compose file and assumed a service was private.

Instead, we checked the effective state of the system.

That leads to one of the security principles we have increasingly adopted while building Tula:

Do not secure what you think you deployed. Secure what is actually running.

Configuration files describe intent.

Running processes, listening sockets, effective SSH configuration, firewall rules, network tests, and logs describe reality.

When the two disagree, reality wins.

Hardening is not something you do after deployment

Our VPS experience reinforced something that applies far beyond SSH.

Security controls become harder to introduce after infrastructure is already carrying production traffic.

A reboot becomes risky.

Closing a port might break something.

Removing password authentication becomes scary because nobody is sure whether every administrator has working keys.

Firewall rules accumulate until nobody is entirely certain why each rule exists.

The easiest server to harden is therefore the one you provisioned ten minutes ago.

For us, VPS provisioning now means provisioning and hardening.

The two are part of the same task.

Whether your VPS comes from HostAfrica or another hosting provider, the provider gives you the machine. Securing the operating system, the services you install, and the interfaces you expose remains an ongoing responsibility shared by whoever operates that infrastructure.

And if there is one lesson we would give anyone purchasing their first VPS, it is this:

The moment your server receives a public IP address, act as though someone has already found it.

Because there is a good chance they have.

#VPS#Engineering
Share

Related posts

We load-tested our Go backend to 2,000 concurrent WebSockets. The app wasn't what broke
We load-tested our Go backend to 2,000 concurrent WebSockets. The app wasn't what broke
Welcome to the Tula blog
Welcome to the Tula blog
Tula

One app for how Africa connects, creates & pays.

Product

  • About
  • Tula Pay rates
  • Blog

Trust & Safety

  • Security
  • Account security
  • Privacy
  • Terms
© 2026 Tula Innovations Africa Limited · Standard Street, Nairobi CBD, Kenya · Incorporated in Kenya
How we handle a new VPS · Tula