Skip to main content

Command Palette

Search for a command to run...

Disabling Direct Root SSH Login Across Multiple Servers

Updated
5 min read
Y
Hi, I’m Yvette — a Berlin-based platform-oriented engineer transitioning into junior roles in platform engineering, DevOps, cloud operations, developer tooling, and observability. This blog is my technical notebook. I write about what I’m learning and building across platform engineering, developer experience, CI/CD automation, cloud operations, APIs, and observability. I care about clarity, reducing noise, and building systems that make work simpler for both users and developers. I’m currently looking for junior or entry-level opportunities where I can continue growing in these areas while contributing to better developer workflows and reliable infrastructure.

Part of my #DevOpsFromZero series — documenting real KodeCloud tasks with the commands, the reasoning, the mistakes, and the principles behind every decision.


The Task

Disable direct SSH root login on all app servers within the Stratos Datacenter.

Three servers. One security change. Higher stakes than anything so far.


Why Does This Matter?

Disabling root SSH login reduces the risk of a complete server takeover.

The root account is like a hotel manager's master access card: it can open every room, office, storage area, and security room. Allowing root to log in directly is like letting someone request that master card at the front desk. If an attacker gets it, they immediately have access to everything.

By disabling direct root SSH login, you force anyone who needs elevated privileges to:

  1. Log in as a named user first

  2. Escalate via sudo only when needed

This creates an audit trail — you know who did what and when. Direct root login leaves none of that.


Understanding SSH Configuration

Before touching any server, it's worth understanding where SSH gets its configuration from — because there are two distinct sides to SSH:

Component Binary Config File Purpose
Client ssh /etc/ssh/ssh_config How you connect to other servers
Daemon sshd /etc/ssh/sshd_config How your server accepts incoming connections

This task is about the daemon — you're changing how the server accepts connections. That means sshd_config is the file you need.


The Infrastructure

Three app servers in the Nautilus project environment:

Server Hostname SSH User
App Server 1 stapp01 tony
App Server 2 stapp02 steve
App Server 3 stapp03 banner

Step-by-Step: What I Did

The process is identical on all three servers. Here it is, using stapp01 as the example.

1. Connect to the Server

ssh tony@stapp01

2. Check the Current State

Before making any change, check what's already in the file:

sudo grep PermitRootLogin /etc/ssh/sshd_config

On stapp01, this returned:

#PermitRootLogin no
# the setting of "PermitRootLogin without-password".
PermitRootLogin yes

Two things stand out here:

  • There's a commented-out line (#PermitRootLogin no) — this is ignored by the daemon

  • There's an active line (PermitRootLogin yes) — this is what the daemon is actually reading

Root login is currently enabled. That's what needs to change.

3. Edit sshd_config

sudo nano /etc/ssh/sshd_config

Find the active PermitRootLogin line and change it to:

PermitRootLogin no

Save and exit (Ctrl+X, Y, Enter).

4. Verify the Change

sudo grep PermitRootLogin /etc/ssh/sshd_config

Expected output:

#PermitRootLogin prohibit-password
# the setting of "PermitRootLogin without-password".
PermitRootLogin no

The active line now says no. The commented lines above are irrelevant — only the uncommented line is read by the daemon.

5. Reload the SSH Daemon

sudo systemctl reload sshd

⚠️ This step is not optional. See the mistakes section below.

6. Exit and Repeat

exit

Repeat steps 1–5 for stapp02 (as steve) and stapp03 (as banner).


restart vs reload — Which One and Why?

Both apply config changes, but they behave differently:

Command What It Does Risk
systemctl restart sshd Stops and starts the service completely Drops all active SSH sessions
systemctl reload sshd Re-reads config without stopping the service Safe — existing sessions stay alive

In production, it is safe to always prefer reload for SSH. Restarting drops active connections — including potentially your own.


The Mistakes

1. Forgetting to Reload After Saving

I edited sshd_config on stapp01, verified the file looked correct, exited, and moved on. The validation check failed — root login was still enabled.

The file was correct. The running daemon was not. It was still operating on the old configuration because I never told it to reload.

💡 A configuration change is not real until the running service has loaded it.

This applies everywhere — nginx, Kubernetes, systemd services, application configs. Editing a file and saving it does nothing to a running process until that process re-reads it.

2. Confusing ssh_config and sshd_config

SSH has both a client config and a daemon config, living side by side in /etc/ssh/. Early on it's easy to reach for ssh_config when you mean sshd_config. The distinction matters:

  • ssh_config → how you connect out

  • sshd_config → how your server lets others connect in

When in doubt: if you're hardening a server, you want sshd_config.


Key Concepts Recap

Concept What It Means
PermitRootLogin no Directive in sshd_config that blocks direct root SSH access
sshd_config Server-side SSH daemon configuration file
ssh_config Client-side SSH configuration file
systemctl reload sshd Re-reads config without dropping active connections
Commented line (#) Ignored by the daemon — only active lines are read
Audit trail Named user logins via sudo create traceable records; root login does not

Closing Thought

Day 1: Not every mistake requires deletion — modify deliberately. Day 2: No error doesn't mean correct behaviour — always verify. Day 3: A configuration change is not real until the running service has loaded it.


#DevOpsFromZero #100DaysOfDevOps #Linux #SSH #Security #DevOps #KodeCloud #Beginner

2 views

DevOps From Zero

Part 1 of 3

A 100-day journey through real DevOps tasks on KodeCloud #100daysofDevOps— documenting the commands, the reasoning, the mistakes, and the principles behind every decision. No polish, just progress.

Up next

Setting Up a Temporary User with an Expiry Date

Part of my #DevOpsFromZero series — documenting real KodeCloud tasks with the commands, the reasoning, the mistakes, and the principles behind every decision. The Task Create a user named mariyam o