# Part 1: Ubuntu System Hardening and Execution Baseline

A fresh Ubuntu installation is designed to be permissive, catering equally to desktop, server, and container workloads without assuming a strict security model.

However, the moment a system is exposed to a network (internal/external), its threat model changes. It transitions from an isolated compute environment into a reachable execution target. What follows is the process of moving from this default, permissive state to a controlled execution environment, ending with an operational monitoring model which will be covered in **Part Two**.

## System Exposure and Baseline Inspection

Before altering the system, you need to understand its current exposure.  
**Network listeners:**

```shell
sudo ss -tulnp
```

This command reveals which processes are actively bound to network interfaces. At this stage, the network layer makes no distinction between intended services (like SSH) and accidental exposure (like a default database binding to `0.0.0.0`).

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/d7add587-6e72-4d4b-8c3d-d9e8bfb17176.png align="center")

**Installed package drift:**

```shell
apt list --upgradable
```

This command shows the gap between what’s installed locally and what the repositories currently provide. That gap is where security debt accumulates.

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/8362eb02-12e4-45f4-ae17-8aabd680cde7.png align="center")

**Enabled services:**

```shell
systemctl list-unit-files --state=enabled
```

This command lists everything configured to execute automatically during boot. Its the most accurate representation of the system's baseline behaviour.

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/128cb36a-899f-4746-88d2-9c9798078afd.png align="left")

* * *

### Package State Alignment

Systems degrade securely over time if they're not consistently aligned with upstream security patches.

**Update alignment:**

```shell
sudo apt update && sudo apt upgrade -y
```

**Verification:**

```shell
apt list --upgradable
```

The expected steady state is an **<mark class="bg-yellow-200 dark:bg-yellow-500/30">empty</mark>** upgrade list. Anything else represents drift that needs to be addressed.

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Expected output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/0c251773-4b7f-4fdb-ba45-5f6fae4b704e.png align="left")

* * *

### Automating Security Updates

Manual update cycles rely on human consistency, which is inherently flawed. Security patch latency (the window between vulnerability disclosure, package availability, and human execution) is where the majority of exploitation occurs.

**Enable unattended updates:**

```shell
sudo apt install unattended-upgrades -y
```

> *Note: you might already have this package installed on your system.*

**Verify behaviour configuration:**

```shell
cat /etc/apt/apt.conf.d/20auto-upgrades
```

This file dictates whether update scheduling is successfully delegated to the system. You should see `APT::Periodic::Update-Package-Lists "1";` and `APT::Periodic::Unattended-Upgrade "1";`.

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/b9f5eddd-5869-46e2-81d6-ef6dcc43c37e.png align="left")

* * *

### SSH as an Execution Boundary

SSH operates as a remote execution boundary at the operating system level. Once access is established, the session effectively represents direct command execution on the host. When password authentication is enabled, that boundary is governed primarily by password strength and any rate-limiting controls enforced by the service.

Removing password authentication reduces the system to key-based identity verification only, but this change must be executed with a verified fallback path already in place.

> **Warning:** Before disabling password authentication, ensure you have successfully generated and copied your SSH public key to `~/.ssh/authorized_keys` on the remote server. Otherwise, you will lock yourself out.

### Precondition: Verify SSH Key Access

From your local machine, confirm you can log in using SSH:

```shell
ssh user@server_ip
```

If a password is still required, key authentication hasn't been configured yet.

You can explicitly test key usage:

```shell
ssh -i ~/.ssh/id_rsa user@server_ip
```

Ensure your public key exists on the remote server:

```shell
cat ~/.ssh/authorized_keys
```

If its missing, copy it using:

```shell
ssh-copy-id user@server_ip
```

Make sure permissions are correct:

```shell
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
```

> <mark class="bg-yellow-200 dark:bg-yellow-500/30">Keep an active SSH session open while making changes, in case rollback is needed.</mark>

**SSH configuration:**  
Modifying the `/etc/ssh/sshd_config` file.

```shell
sudo sed -i 's/^#*PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#*PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
```

The commands above will ensure the following directives are set:

```plaintext
PasswordAuthentication no
PermitRootLogin no
```

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/41441b3a-072d-4a63-90db-69b0715185a3.png align="left")

**Apply and verify the changes:**

```shell
sudo systemctl restart ssh
sudo sshd -T | grep -i passwordauthentication
```

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/9b45eff8-97f3-49f3-8e18-89a273488692.png align="left")

After this change, SSH access depends entirely on key-based authentication. Password login is no longer accepted by the service configuration.

**<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example of key-based authentication:</mark>**

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/d2140bde-56c4-4453-a4b3-6cfbce2dcca3.png align="left")

* * *

### The Firewall as Traffic Definition

A firewall does not inherently secure a vulnerable application, but it enforces strict network boundaries, defining exactly which paths are valid.

**Configure UFW (Uncomplicated Firewall):**

```shell
sudo ufw allow OpenSSH
sudo ufw enable
```

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/ea1cfde1-169f-412c-8c31-a653896af182.png align="left")

Verification:

```shell
sudo ufw status verbose
```

<mark class="bg-yellow-200 dark:bg-yellow-500/30">Example output:</mark>

![](https://cdn.hashnode.com/uploads/covers/68a5e6b9bf57f369891da8e0/9f008b51-460e-46a5-a416-e95ebd329811.png align="left")

> Note: If SSH is not explicitly allowed before enabling the firewall, all active and future connections will be dropped.

* * *

### Service Minimisation

Every enabled system service increases the memory footprint, dependency surface during boot, and potential attack vectors. There is no distinction between a service you deliberately intended to expose and one installed silently as a dependency unless you actively audit them.

Review your active listeners (`sudo ss -tulnp`) and disable anything unnecessary:

```shell
sudo systemctl disable --now <service_name>
```

### The Operational Monitoring Layer

At this stage, the system is secured but not yet structured for robust observability.

Linux produces vital telemetry, natively split between **runtime event streams** and **persisted file logs** (traditionally in `/var/log`). Historically, managing this was fragmented. Systemd unifies this execution and monitoring model.

### Why systemd supersedes legacy execution

Historically, service execution was handled by a mix of SysV init scripts, cron-based scheduling, and ad-hoc supervision tools. This resulted in:

*   No unified dependency graph.
    
*   Inconsistent startup ordering.
    
*   Manual supervision required for long-running processes.
    
*   Fragmented logging channels.
    

Systemd is often reductively called a "service manager", but it is actually a unified execution and dependency management framework. Instead of executing disparate scripts, systemd treats the system as a strict dependency graph of managed units.

**Core systemd components:**

*   **PID 1:** The systemd daemon itself, controlling the boot lifecycle.
    
*   **Unit files:** Declarative service definitions.
    
*   **Journald:** The integrated, binary log collection subsystem.
    
*   **Timers:** The scheduled execution model.
    

### Why systemd timers replace cron

Cron operates on a rudimentary premise: *"<mark class="bg-yellow-200 dark:bg-yellow-500/30">execute command X at time Y</mark>"*.  
It has no contextual awareness of whether the previous execution is still running, if the system is overloaded, or if prerequisite services are available.

Systemd timers resolve these operational blind spots:

*   **Contextual execution:** Execution is tied strictly to service units, allowing for dependency mapping (e.g., "only run this script if the network is up").
    
*   **State awareness:** You can query execution state reliably via `systemctl`.
    
*   **Built-in supervision:** Overlapping executions can be prevented automatically.
    
*   **Unified logging:** Standard output (stdout) and errors (stderr) from timed tasks are captured automatically by `journald`, ensuring scheduled tasks are monitored exactly like persistent daemons.
    

The shift from cron to systemd timers reflects a move toward system-aware scheduling that integrates with service state, logging, and lifecycle management. Because systemd inherently understands execution state, manages dependencies, and natively captures logs, it provides the robust execution engine required for the active alerting architecture we will implement in **Part Two**.
