PROFILE / INDEX© Clément Depernet / 2026

← Back to index

12 Aug 20262 min read

Hardening an Oracle Cloud instance in 30 minutes

A freshly provisioned Oracle Cloud VM is reachable from the entire internet within seconds of creation. Here is the sequence I run before deploying anything on it.

intro#hardening#cloud#linux#ssh

An Oracle Cloud Always Free instance ships with two stacked firewalls — the network Security List and system-level iptables — plus an Ubuntu image that, by default, leaves INPUT at ACCEPT for most of what the Security List lets through. Knowing which of the two blocks what saves you an hour of debugging a port that looks “open” but never answers.

Threat model, in three lines

What hits a public IP in the minutes after it goes live:

  • automated, continuous SSH brute-forcing from botnets;
  • systematic port scanning, high ports included;
  • HTTP discovery (/.env, /.git/config, /wp-login.php) the moment a web service responds.

Nothing targeted — just opportunistic background noise. The good news: generic measures handle generic noise.

1. Remove password authentication

Best effort-to-payoff measure by a wide margin. Brute-forcing only works if there is a password to find.

sudo install -d -m 755 /etc/ssh/sshd_config.d
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf > /dev/null <<'CONF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
AllowUsers ubuntu
X11Forwarding no
CONF

sudo sshd -t && sudo systemctl reload ssh

The sshd -t before reload is not decoration: it validates the config. A typo can otherwise make sshd refuse to restart — and you lose your only access.

Always keep a second SSH session open while editing sshd_config. If the new config locks you out, the already-established session is your way back in.

2. Understand the two firewalls

The Security List lives in the OCI console; until a port is allowed there as Ingress, the packet never reaches the VM. Meanwhile Oracle’s Ubuntu image ships with preloaded iptables rules — the classic trap: the Security List allows the port, but iptables still rejects it. Insert your rules before the trailing REJECT:

sudo iptables -I INPUT 6 -p tcp --dport 80  -m state --state NEW -j ACCEPT
sudo iptables -I INPUT 7 -p tcp --dport 443 -m state --state NEW -j ACCEPT
sudo netfilter-persistent save

3. Shrink the exposed surface

sudo ss -tulpn | grep LISTEN

Anything listening on 0.0.0.0 without reason should move to 127.0.0.1 or be stopped. And beware Docker: -p 8080:80 publishes on all interfaces and bypasses iptables. Bind to loopback when a service needn’t be public:

ports:
  - "127.0.0.1:8080:80"   # not "8080:80"

4. Verify, don’t assume

From an outside machine:

nmap -Pn -p- --min-rate 1000 <PUBLIC_IP>
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no ubuntu@<IP>

The last command must fail immediately. If it prompts for a password, your SSH config was not reloaded.

What this does not cover

This hardens network exposure and system access. It says nothing about application security: an injection flaw will ride in through the very 443 you just legitimately opened. HTTP headers, CSP and input validation are a separate effort — and that is where most actually-exploited vulnerabilities live.

SEC / OPS / CD-01

Astro + Tailwind, containerised, served behind Caddy · Inspiration · Startup theme: Severance (Theodore Shapiro) · Cute favicon, isn't it?

READY / CD-01