Operating Systems AWS

Ubuntu Server 22.04 on AWS User Guide

| Product: Ubuntu Server 22.04 LTS on AWS

Overview

This image is Ubuntu Server 22.04 LTS (Jammy Jellyfish), one software version of the cloudimg Hardened Ubuntu 22.04 & 24.04 LTS AMIs: Minimal, LVM, STIG listing on AWS Marketplace. Every release and variant in the listing is described at /guides/ubuntu-server-aws/.

Host firewall enabled (ufw, inbound SSH only): open app ports in both the security group and ufw. Opening a port in the security group alone is not enough.

What cloudimg does to the image:

  • Remediated with the OpenSCAP CIS Level 1 Server profile. The build applies the SCAP Security Guide 0.1.82 profile cis_level1_server with oscap --remediate, reboots, then runs a scored evaluation. The build fails if fewer than 95% of the scored rules pass; this version scored 338 of 343 rules (98.5%). The report ships in the image at /usr/share/doc/cloudimg/cis-l1-report.html. This is a measurement made by open-source tooling, not a certification or accreditation by the benchmark's publisher or any other body, and these images are not CIS Hardened Images.
  • 6 rules waived, each with its reason (listed below and in /usr/share/doc/cloudimg/cis-l1-waivers.txt): rules that would lock you out of an EC2 instance or need a password baked into every copy of the image.
  • Fully patched at build time. The build fails if any update is outstanding or a reboot is pending.
  • Automatic security updates stay on (unattended-upgrades).
  • Key-only SSH as ubuntu. Password login is off, root login is refused, the root account is locked and no account has a password.
  • IMDSv2 only. The AMI is marked IMDSv2-only, so instances launched from it require session tokens for instance metadata.
  • Host firewall on (ufw): inbound SSH only, outbound open so the SSM agent, updates, DNS and NTP work.
  • AppArmor enforcing, chrony on the Amazon Time Sync Service (169.254.169.123, no internet access needed), and the AWS Systems Manager (SSM) agent installed from its Debian package (snapd and every snap are removed).
  • Nothing shared between instances. No SSH keys, host keys, machine ID or credentials are baked in; cloud-init creates them on each instance's first boot.
  • No swap, and SSH is the only service listening on the network (the DNS stub resolver, chrony and the local mail transfer agent listen on loopback only).

Ubuntu 22.04 LTS receives standard security maintenance from Canonical until 2027. For a longer runway on a new deployment, the Ubuntu 24.04 LTS versions of the same listing are maintained until 2029.

Connecting to your instance

Connect over SSH as the default login user for the operating system variant you launched:

Operating system variant SSH login user
Ubuntu Server 22.04 LTS ubuntu

Launch from AWS Marketplace

  1. Open the listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for full control), then in Version choose Ubuntu Server 22.04 LTS - YYYY-MM-DD (the date is when the version was added) and your Region.
  4. Instance type: m5.large is recommended; any current x86_64 type works. The software charge is $0.04 per vCPU per hour.
  5. Key pair: choose your EC2 key pair. It is the only way in. RSA and ED25519 keys both work.
  6. Security group: allow inbound TCP 22 from your own IP address or network only.
  7. Storage: the root volume is 8 GiB gp3 by default. Choose a larger size if you need one; cloud-init grows the root filesystem to fill it on first boot.
  8. Metadata: leave the defaults. The image already requires IMDSv2. Then choose Launch.

From the AWS CLI (use the AMI ID the Marketplace launch page shows for your Region):

aws ec2 run-instances --image-id ami-xxxxxxxxxxxxxxxxx --instance-type m5.large \
  --key-name my-key --security-group-ids sg-xxxxxxxx --subnet-id subnet-xxxxxxxx

Connect over SSH

ssh -i ~/.ssh/my-key.pem ubuntu@<public-ip>

ubuntu has passwordless sudo. There is no password on any account. AWS Systems Manager Session Manager also works: the SSM agent is installed and running (attach an instance profile with AmazonSSMManagedInstanceCore).

The SSH server allows four authentication attempts per connection. If your SSH agent holds several keys and the connection fails with Too many authentication failures, add -o IdentitiesOnly=yes so SSH offers only the key you name.

Confirm the release and kernel:

grep -E '^(PRETTY_NAME|VERSION_ID|VERSION_CODENAME)=' /etc/os-release
uname -r
PRETTY_NAME="Ubuntu 22.04.5 LTS"
VERSION_ID="22.04"
VERSION_CODENAME=jammy
6.8.0-1066-aws

The -aws kernel is Canonical's kernel flavour for EC2. Its version moves forward as Ubuntu publishes updates; the image ships the kernel current on its build date.

Ubuntu Server 22.04 release, kernel and IMDSv2 check on an EC2 instance

IMDSv2

The AMI carries ImdsSupport=v2.0, so every instance launched from it gets HttpTokens=required. Check it on the instance:

imds_token=$(curl -s -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 300' http://169.254.169.254/latest/api/token)
curl -s -H "X-aws-ec2-metadata-token: $imds_token" http://169.254.169.254/latest/meta-data/instance-id; echo
curl -s -o /dev/null -w '%{http_code}\n' http://169.254.169.254/latest/meta-data/instance-id

The first request (with a token) returns your instance ID; the second (IMDSv1, no token) is refused with 401:

i-0123456789abcdef0
401

Software that still uses IMDSv1 must be updated to request a token (current AWS SDKs and CLIs already do). If you must allow IMDSv1 temporarily, change it per instance with aws ec2 modify-instance-metadata-options --http-tokens optional; we do not recommend it.

View the compliance report

The evaluation the build ran is in /usr/share/doc/cloudimg/cis-l1-report.html (HTML), /usr/share/doc/cloudimg/cis-l1-arf.xml (machine-readable ARF) and /usr/share/doc/cloudimg/cis-l1-summary.json (counts, pass rate, threshold, SSG version and checksums). The build host's name and addresses are replaced in the shipped copy.

ls /usr/share/doc/cloudimg/
python3 -c "import json; d=json.load(open('/usr/share/doc/cloudimg/cis-l1-summary.json')); print(d['profileLabel'], d['ssgVersion'], '-', d['pass'], 'of', d['scored'], 'scored rules pass', '(' + str(d['ratePct']) + '%)', '- waived', d['waived'])"
cis-l1-arf.xml
cis-l1-report.html
cis-l1-summary.json
cis-l1-tailoring.xml
cis-l1-waivers.txt
scap-security-guide-LICENSE.txt
CIS Level 1 Server 0.1.82 - 338 of 343 scored rules pass (98.5%) - waived 6

The shipped OpenSCAP CIS Level 1 Server evaluation summary and the rules that do not pass

Copy the HTML report to your workstation and open it in a browser:

scp -i ~/.ssh/my-key.pem ubuntu@<public-ip>:/usr/share/doc/cloudimg/cis-l1-report.html .

The pass rate is pass / (pass + fail + error + unknown). Rules that are not applicable to this machine, not selected, or waived are not counted.

The five rules that do not pass

python3 -c "import json; [print(r) for r in json.load(open('/usr/share/doc/cloudimg/cis-l1-summary.json'))['failingRules']]"
ensure_root_access_controlled
service_nftables_enabled
set_nftables_loopback_traffic
set_nftables_table
set_ufw_default_rule

What each one means on this image:

  • ensure_root_access_controlled: the Ubuntu 22.04 version of this check passes only when root has a password hash. A root password on the image would be the same secret on every instance, so root has no password at all and is locked instead (sudo passwd -S root shows L, below). Nobody can log in as root; administrators use sudo.
  • service_nftables_enabled, set_nftables_loopback_traffic and set_nftables_table: these belong to the profile's nftables firewall option. The firewall on this image is ufw, and nftables.service is masked so it can never start and flush ufw's rules at boot. Running two firewall front ends at once is what the benchmark itself warns against.
  • set_ufw_default_rule: the benchmark wants outbound traffic denied by default. This image allows outbound on purpose (see the deliberate deviation below).

Re-run the same evaluation on your instance at any time (a few minutes; nothing is changed):

sudo oscap xccdf eval --tailoring-file /usr/share/doc/cloudimg/cis-l1-tailoring.xml \
  --profile xccdf_co.cloudimg_profile_cis-l1_ec2_single \
  --report ~/cis-l1-rescan.html /usr/share/xml/scap/ssg/content/ssg-ubuntu2204-ds.xml

oscap exits with status 2 when any rule fails; that is expected and the HTML report is still written. As you install software and change settings, re-run it to see how your changes affect the result.

Waived rules

These rules of the profile are deselected through the tailoring file (/usr/share/doc/cloudimg/cis-l1-tailoring.xml) and are not scored. Everything else the profile selects is remediated and scored. The tailoring also selects ufw as the profile's firewall option and chrony as its time service.

grep -v '^#' /usr/share/doc/cloudimg/cis-l1-waivers.txt | cut -f1,2
partition_for_tmp   [single-volume-partitions]
grub2_password  [serial-console-and-boot]
grub2_uefi_password [serial-console-and-boot]
accounts_maximum_age_login_defs [key-only-account-aging]
account_disable_post_pw_expiration  [key-only-account-aging]
sshd_limit_user_access  [ssh-access-list]
Rule Category Why it is waived on EC2
partition_for_tmp single-volume-partitions The standard and Minimal versions boot from one EBS root volume, so /tmp, /var, /var/log, /var/log/audit, /var/tmp and /home live on the root filesystem. The LVM versions provide every one of these as a separate volume with the benchmark's mount options.
grub2_password serial-console-and-boot A boot-loader or root password would have to be baked into the image, giving every instance the same credential. Root stays locked and no password exists. EC2 Serial Console and single-user recovery need a password you set yourself on your own instance (see Emergency access).
grub2_uefi_password serial-console-and-boot As above, for the UEFI boot loader.
accounts_maximum_age_login_defs key-only-account-aging Password-age and inactivity defaults apply to every account cloud-init creates at launch. Those accounts are key-only and have no password, so an age limit expires them and locks the SSH key out. Accounts that do have a password are still aged.
account_disable_post_pw_expiration key-only-account-aging As above, for the inactivity default.
sshd_limit_user_access ssh-access-list An AllowUsers/AllowGroups list fixed at build time would refuse SSH to accounts you create with cloud-init user-data. SSH is key-only, root login is refused, and your security group restricts the source addresses.

Deliberate deviation (scored as a failure): ufw denies all inbound traffic except SSH (22/tcp) but allows outbound traffic, so set_ufw_default_rule reports fail and counts against the score. A default-deny outbound policy would cut the SSM agent off from its endpoints, cloud-init and the instance from the metadata service (169.254.169.254), and the instance from DNS, NTP and package updates.

Host firewall and SSH

ufw is active and allows inbound SSH only; SSH accepts keys only:

sudo ufw status verbose
sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|pubkeyauthentication) '
sudo passwd -S root
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
Anywhere on lo             ALLOW IN    Anywhere
22/tcp                     ALLOW IN    Anywhere
68/udp                     ALLOW IN    67/udp                     # DHCP client: replies from a DHCP server (port 67)
Anywhere                   DENY IN     127.0.0.0/8
Anywhere (v6) on lo        ALLOW IN    Anywhere (v6)
22/tcp (v6)                ALLOW IN    Anywhere (v6)
Anywhere (v6)              DENY IN     ::1

Anywhere                   ALLOW OUT   Anywhere on lo
Anywhere (v6)              ALLOW OUT   Anywhere (v6) on lo

permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
root L 10/14/2026 0 99999 7 -1

root L means the root password is locked. The 68/udp rule admits only the replies of the VPC's DHCP server to the instance's own DHCP client (the client that keeps the instance's private address); ufw already accepts those replies, and the explicit rule opens nothing new.

ufw allowing inbound SSH only, and key-only SSH settings

To serve anything else, open the port in both your security group and the host firewall, for example HTTPS:

sudo ufw allow 443/tcp
sudo ufw status verbose

Listening ports

sudo ss -tlnu
Netid State  Recv-Q Send-Q      Local Address:Port Peer Address:PortProcess
udp   UNCONN 0      0           127.0.0.53%lo:53        0.0.0.0:*
udp   UNCONN 0      0      172.31.83.113%ens5:68        0.0.0.0:*
udp   UNCONN 0      0               127.0.0.1:323       0.0.0.0:*
udp   UNCONN 0      0                   [::1]:323          [::]:*
tcp   LISTEN 0      128               0.0.0.0:22        0.0.0.0:*
tcp   LISTEN 0      4096        127.0.0.53%lo:53        0.0.0.0:*
tcp   LISTEN 0      100             127.0.0.1:25        0.0.0.0:*
tcp   LISTEN 0      128                  [::]:22           [::]:*
tcp   LISTEN 0      100                 [::1]:25           [::]:*

Only sshd (22/tcp) listens on the network. The DNS stub resolver (53), chrony's control port (323) and the local mail relay (25, for system mail) listen on loopback only, and UDP 68 on the private address is the DHCP client. Your own address appears in place of 172.31.83.113.

Services

systemctl is-active ssh ufw apparmor amazon-ssm-agent chrony
systemctl is-enabled unattended-upgrades
chronyc -n sources | head -3
active
active
active
active
active
enabled
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* 169.254.169.123               3   4   377    21  -4333ns[-4210ns] +/-  537us

^* marks the source chrony is synchronised to: the Amazon Time Sync Service at 169.254.169.123, which every EC2 instance reaches without internet access, so time stays correct in a private subnet too. The Ubuntu NTP pool servers in /etc/chrony/chrony.conf are kept as fallbacks.

Check that the system booted cleanly and that snapd is gone:

systemctl is-system-running
dpkg -s snapd >/dev/null 2>&1 && echo "snapd: installed" || echo "snapd: not installed"
running
snapd: not installed

Patching

unattended-upgrades installs security updates automatically. To see how many updates are waiting:

apt list --upgradable 2>/dev/null | grep -c upgradable || true
0

An instance launched soon after the image was built shows 0; later launches show the updates released since. To install everything now:

sudo apt-get update && sudo apt-get -y upgrade

If /var/run/reboot-required exists afterwards, a new kernel or core library was installed: reboot when convenient with sudo reboot.

Disk size and swap

df -h /
swapon --noheadings --show | wc -l

With the default 8 GiB root volume:

Filesystem      Size  Used Avail Use% Mounted on
/dev/root       7.6G  2.0G  5.6G  27% /
0

cloud-init grows the root partition and filesystem to the volume size on first boot. To grow later, increase the EBS volume size in the console or with aws ec2 modify-volume, then reboot (or run sudo growpart /dev/nvme0n1 1 && sudo resize2fs /dev/nvme0n1p1). The image has no swap; add a swap file yourself if your workload needs one.

Emergency access

There is no password on any account, by design. EC2 Serial Console and single-user recovery need a local password: set one for root on your own instance before you need it, and keep it in your secrets store:

sudo passwd root

Session Manager (SSM) is the other way in if SSH is unavailable.

Licensing

Ubuntu is free and open source software: a distribution of thousands of packages, each under its own licence (GPL, LGPL, MIT, BSD, Apache 2.0 and others); each package's terms are in /usr/share/doc/<package>/copyright. No subscription or licence key is needed. The compliance tooling is OpenSCAP (LGPL 2.1 or later) and SCAP Security Guide content (BSD 3-Clause, licence text in /usr/share/doc/cloudimg/scap-security-guide-LICENSE.txt).

Support

cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. Ubuntu is a registered trademark of Canonical Ltd. CIS and CIS Benchmarks are trademarks of the Center for Internet Security, Inc. cloudimg is not affiliated with or endorsed by Canonical or CIS.