Operating Systems AWS

AlmaLinux 9 with GNOME Desktop on AWS User Guide

| Product: AlmaLinux 9 with GNOME Desktop on AWS

Overview

This image is AlmaLinux 9 with the GNOME desktop. It is one software version of the cloudimg Hardened Debian & Linux Desktops AMIs listing on AWS Marketplace. Every desktop in the listing is described at /guides/linux-desktop-aws/.

It is the cloudimg AlmaLinux 9 image (genuine AlmaLinux 9 stable, a numbered 9.N release, never a beta) with AlmaLinux's own Server with GUI environment added: GNOME 40 with the GDM login screen, running on the X.Org server, from the AlmaLinux BaseOS and AppStream repositories. You reach it with any VNC viewer through TigerVNC, the VNC server AlmaLinux ships in AppStream. Each user gets their own full GNOME session on the instance.

VNC ships switched off, and the image holds no password of any kind. No account password, no VNC password and no TLS key are baked in. You set your own two passwords over SSH, then turn VNC on with one command. VNC then listens on localhost only, so you always connect through an SSH tunnel and port 5901 is never open to the internet.

What cloudimg does to the image:

  • The GNOME desktop from the AlmaLinux repositories only. The image includes GDM, GNOME Shell, Files, Terminal, Firefox, Text Editor, System Monitor and Settings, installed in one transaction with weak dependencies off. There is no third-party repository and no EPEL. Things no EC2 instance can use are left out: Wi-Fi firmware and the Hyper-V and VMware guest agents.
  • Printing is not included. The CUPS print server and its filters are left out and excluded from dnf (see Printing).
  • TigerVNC, off and locked down. A system-wide policy file forces every VNC desktop to listen on localhost only and to offer only security types that need the VNC password. A user's own TigerVNC settings cannot override it. The cloudimg-vnc helper refuses to start a desktop for an account that has no password or no VNC password.
  • Remediated with the OpenSCAP CIS Level 1 Server profile. The build applies the SCAP Security Guide 0.1.82 profile cis_server_l1 with oscap --remediate after the desktop is installed. That way its GNOME rules (login banner, no user list, screen lock, no automount) and its Avahi, printing and Bluetooth rules apply to this desktop. The build then reboots and runs a scored evaluation, and it fails if fewer than 95% of the scored rules pass. The report ships in the image at /usr/share/doc/cloudimg/cis-l1-report.html. This is a measurement made by open-source tooling. It is not a certification or accreditation by the benchmark's publisher or any other body, and these images are not CIS Hardened Images.
  • Fully patched at build time, with automatic security updates left on (dnf-automatic).
  • Key-only SSH as ec2-user. Password login over SSH is off and stays off after you set a desktop password. Root login is refused and the root account is locked.
  • Platform hardening. The image runs the firewalld host firewall (inbound SSH only) with SELinux enforcing. It also runs chrony on the Amazon Time Sync Service and the AWS Systems Manager agent.

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
AlmaLinux 9 with GNOME Desktop ec2-user

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 Launch from EC2 Console (or One-click launch). In Version, choose AlmaLinux 9 with GNOME Desktop - YYYY-MM-DD HH:MM, then choose your Region.
  4. Instance type: m5.large (2 vCPU, 8 GiB) is recommended. The login screen plus one GNOME session fits in less, but a browser and a few applications need the room. There is no GPU requirement: the desktop renders in software.
  5. Key pair: choose your EC2 key pair. It is the only way in.
  6. Security group: allow inbound TCP 22 from your own IP address only. That is the only port this image needs: VNC travels inside SSH.
  7. Storage: the root volume is 16 GiB gp3 by default. Choose a larger size if you will install applications. cloud-init grows the root filesystem to fill it on first boot.
  8. Choose Launch.

Step 1: Connect over SSH and check the desktop

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

ec2-user has passwordless sudo, and no account has a password. Check the release, that the instance booted to the graphical target with the GNOME login screen (GDM) running on X.Org, and the installed desktop and VNC packages:

cat /etc/almalinux-release
systemctl get-default
systemctl is-active gdm
grep '^WaylandEnable' /etc/gdm/custom.conf
rpm -q --qf '%{NAME} %{VERSION}\n' gdm gnome-shell mutter xorg-x11-server-Xorg tigervnc-server
AlmaLinux release 9.8 (Olive Jaguar)
graphical.target
active
WaylandEnable=False
gdm 40.1
gnome-shell 40.10
mutter 40.9
xorg-x11-server-Xorg 1.20.11
tigervnc-server 1.15.0

Package versions move forward as AlmaLinux publishes updates. RHEL 9 TigerVNC desktops are X11 sessions, which is why GDM runs with Wayland disabled.

An EC2 instance has no display device that AlmaLinux 9 can drive, so the GDM login screen on the instance's own console runs on the X.Org dummy video driver. Before GDM starts, /usr/local/libexec/cloudimg-xorg-headless writes /etc/X11/xorg.conf.d/90-cloudimg-headless-dummy.conf when there is no display device, and removes it when there is one (for example on a GPU instance with its driver installed). Your VNC desktops do not use that file.

Step 2: Check that VNC is off

A new instance has VNC switched off. The helper shows the whole picture in one command:

sudo cloudimg-vnc status
grep -vE '^#|^$' /etc/tigervnc/vncserver-config-mandatory
sudo ss -tlnH | awk '{print $4}' | sort
VNC policy: localhost only, security types tlsvnc,vncauth (/etc/tigervnc/vncserver-config-mandatory)
xvnc.socket (unauthenticated mode): disabled / inactive
VNC desktops configured: none
listening on VNC ports 5900-5999: none
users with a VNC password set: none (each user runs: vncpasswd)
localhost
securitytypes=tlsvnc,vncauth
0.0.0.0:22
[::]:22

The policy file forces two settings on every user, and TigerVNC reads it after each user's own configuration, so a user's settings cannot undo it:

  • localhost makes the VNC server listen on 127.0.0.1 and ::1 only.
  • securitytypes=tlsvnc,vncauth offers only security types that require the VNC password. None (no password) is never offered.

SSH on port 22 is the only listener. The unauthenticated inetd mode xvnc.socket is disabled.

The helper lives at /usr/local/sbin/cloudimg-vnc. On AlmaLinux, sudo replaces your PATH with its own secure_path, which does not include /usr/local/sbin, so the image also ships /usr/sbin/cloudimg-vnc as a symlink and sudo cloudimg-vnc works as written. Its subcommands are status, enable [<user>], disable [<user>] and details. <user> defaults to ec2-user.

Step 3: Set your two passwords

Neither password exists until you create it, and both belong to you.

First your account password. The VNC session logs you straight into GNOME, but the desktop asks for this password when the screen locks and when an application needs administrator rights. Run this and enter the new password twice:

sudo passwd ec2-user

Then your VNC password, which your VNC viewer asks for. Run vncpasswd as yourself, without sudo, enter the password twice, and answer n to the view-only question:

vncpasswd
Password:
Verify:
Would you like to enter a view-only password (y/n)? n
A view-only password is not used

VNC's own password scheme uses only the first 8 characters of the password, which is why the image never exposes VNC to the network. vncpasswd stores it in ~/.config/tigervnc/passwd with mode 0600. SSH stays key only: neither password enables password login over SSH.

Step 4: Start your VNC desktop

sudo cloudimg-vnc enable
VNC desktop for ec2-user enabled on display :1: listening on localhost port 5901 only
from your own machine: ssh -N -L 5901:localhost:5901 ec2-user@<instance-ip>
then point a VNC viewer at localhost:5901 and enter ec2-user's VNC password

The helper adds :1=ec2-user to /etc/tigervnc/vncserver.users, runs systemctl enable --now vncserver@:1.service, waits until port 5901 is listening, and stops the desktop again if the port is bound anywhere other than localhost. The desktop starts again after a reboot.

If a password is missing, it refuses and tells you which one:

refusing to start a VNC desktop for ec2-user: the account has no password yet.
The GNOME lock screen and administrator prompts inside the desktop ask for it. Set it first:
  sudo passwd ec2-user
refusing to start a VNC desktop for ec2-user: no VNC password is set.
As ec2-user (without sudo) run:  vncpasswd

Step 5: Connect through an SSH tunnel

On your own workstation, open the tunnel and leave it running:

ssh -i ~/.ssh/my-key.pem -N -L 5901:localhost:5901 ec2-user@<public-ip>

Then point your VNC viewer at localhost:5901 (TigerVNC and RealVNC accept localhost:5901; some viewers want the display form localhost:1; on macOS open Screen Sharing and connect to vnc://localhost:5901). Enter your VNC password, and your GNOME desktop opens.

TigerVNC Viewer marks the password dialog "This connection is not secure". It says that because TigerVNC's TLSVnc security type uses anonymous TLS, so there is no certificate for the viewer to verify. Through the SSH tunnel the connection is encrypted and the server is authenticated by your SSH host key check, which is why the image allows VNC over the tunnel only.

Connecting to the instance's public address on port 5901 does not work, by design: the server listens on localhost only, firewalld admits SSH only, and the security group needs only port 22.

Step 6: Use the desktop

Your viewer shows the GNOME desktop at 1920x1080. The Activities button in the top-left corner (or the Super key) opens the overview with the dash and the search field:

The GNOME desktop of the AlmaLinux 9 instance in a TigerVNC session reached through the SSH tunnel, with the Activities button, the clock and the system menu in the top bar

The grid button at the end of the dash (or Super+A) lists the installed applications, among them Settings, Text Editor, System Monitor, Disks, Logs and Document Viewer, with Firefox, Files, Software, Help and Terminal in the dash:

The GNOME application grid in the VNC session listing Videos, Calculator, Text Editor, Settings, System Monitor, Characters, Screenshot, Cheese, Fonts, Disk Usage Analyzer, Disks, Document Viewer, Image Viewer and Logs above the dash

A terminal inside the desktop runs on display :1, and the VNC server is bound to the loopback addresses only:

GNOME Terminal inside the VNC desktop showing AlmaLinux release 9.8, display :1 and the VNC server listening on 127.0.0.1:5901 and [::1]:5901 only

To add software, use dnf in a terminal as on any AlmaLinux machine, for example sudo dnf install -y libreoffice, then open the application from the grid.

Closing your VNC viewer leaves the session running on the instance. Connect again and you return to it.

Desktops for more users

Each user gets their own display and port. Create the user with a password, have them set their VNC password, then enable their desktop. The helper assigns the next free display (:2 is port 5902):

sudo useradd -m alice
sudo passwd alice
sudo -u alice -H vncpasswd
sudo cloudimg-vnc enable alice

Alice then tunnels her own port, for example ssh -N -L 5902:localhost:5902 alice@<public-ip>, once her SSH public key is in ~alice/.ssh/authorized_keys.

Turn VNC off again

sudo cloudimg-vnc disable
VNC desktop for ec2-user disabled: display :1 removed, nothing listening on port 5901

This stops the session, disables vncserver@:1.service and removes the mapping, so nothing listens after a reboot. sudo cloudimg-vnc status shows the state at any time.

Security posture

Confirm that SSH is key only, root is locked, no account has a password until you set one, the host firewall admits SSH only, SELinux is enforcing and the instance never suspends:

sudo sshd -T | grep -E '^(passwordauthentication|permitrootlogin|pubkeyauthentication) '
sudo passwd -S root | awk '{print $1, $2}'
sudo awk -F: '$2 !~ /^[!*]/ && $2 != "" {n++} END {print "accounts with a password: " n+0}' /etc/shadow
sudo firewall-cmd --list-services
getenforce
busctl call org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager CanSuspend
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
root LK
accounts with a password: 0
dhcpv6-client ssh
Enforcing
s "na"

The firewall's public zone allows ssh and dhcpv6-client. The second only admits the replies to the instance's own IPv6 DHCP client, which is how an instance in an IPv6-enabled subnet gets its address. After Step 3, the password count reads 1 (your own account). root LK means the root account is locked, and na means logind will not suspend the instance, so the desktop menu offers Power Off and Log Out but never Suspend (a suspended instance stops answering while EC2 still reports it running).

Each instance also gets its own identity at first boot: SSH host keys, /etc/machine-id and the NVMe host identity (/etc/nvme/hostnqn, /etc/nvme/hostid) are generated on the instance, so no two instances share them.

Known issue: TigerVNC and CVE-2025-49177

CVE-2025-49177 is not fixed in the TigerVNC package on AlmaLinux 9, and Red Hat has said it will not fix it on RHEL 9. The Red Hat Security Data API lists tigervnc on Red Hat Enterprise Linux 9 as "Will not fix", severity Important, CVSS 3.1 score 6.1 with a local attack vector (AV:L). It is a flaw in the X server's XFIXES extension: the XFixesSetClientDisconnectMode handler does not validate the request length, so a connected X client can read memory left over from earlier requests. No AlmaLinux 9 advisory fixes it.

What that means on this image: Xvnc accepts X clients only through its local socket and the desktop user's own X authority cookie, with no X11 TCP port, and VNC is off until you enable it. If your security policy does not accept an unfixed vendor CVE, do not enable VNC on this image. If AlmaLinux publishes a fixed package, dnf-automatic applies it automatically.

View the compliance report

The evaluation the build ran ships in /usr/share/doc/cloudimg/ in three forms:

  • cis-l1-report.html, the HTML report
  • cis-l1-arf.xml, the machine-readable ARF results
  • cis-l1-summary.json, with the counts, pass rate, threshold, SSG version and checksums

The build host's name and addresses are replaced in the shipped copy.

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'])"
python3 -c "import json; [print(r) for r in json.load(open('/usr/share/doc/cloudimg/cis-l1-summary.json'))['failingRules']]"
CIS Level 1 Server 0.1.82 - 268 of 269 scored rules pass (99.6%) - waived 6
ensure_journald_and_rsyslog_not_active_together

The one failing rule wants one logging system, not both. AlmaLinux runs journald and rsyslog together by default: journald keeps the structured system journal (persistent, in /var/log/journal), and rsyslog writes the traditional text logs (/var/log/messages, /var/log/secure, /var/log/cron) that many log shippers and audit procedures read. The image keeps both so neither kind of tooling loses its logs. If you standardise on one, for example the journal only, run sudo systemctl disable --now rsyslog on your instance and re-run the evaluation below.

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

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

You can re-run the same evaluation on your instance at any time. It takes about 5 to 10 minutes and changes nothing. oscap exits with status 2 when any rule fails, and the report is still written:

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

Waived rules

These rules of the profile are deselected through the tailoring file /usr/share/cloudimg/openscap/tailoring.xml and are not scored. The reasons are in /usr/share/doc/cloudimg/cis-l1-waivers.txt. Everything else the profile selects is remediated and scored.

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]
ensure_root_password_configured [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 Why it is waived on EC2
partition_for_tmp The image boots from one EBS root volume, so /tmp, /var, /var/log, /var/log/audit, /var/tmp and /home live on the root filesystem.
grub2_password, ensure_root_password_configured 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.
accounts_maximum_age_login_defs, account_disable_post_pw_expiration Password-age defaults apply to every account cloud-init creates at launch. Those accounts are key only and have no password, so an age limit would expire them and lock the SSH key out. Accounts that do have a password (yours, after Step 3) are still aged.
sshd_limit_user_access 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.

Networking and services that ship switched off

NetworkManager manages the EC2 network interface, and cloud-init writes its connection profile at first boot:

nmcli -t -f DEVICE,TYPE,STATE device
eth0:ethernet:connected
lo:loopback:connected (externally)

The Server with GUI group brings several network services that this image leaves switched off:

for u in cockpit.socket avahi-daemon.service cups.service bluetooth.service ModemManager.service rpcbind.socket xvnc.socket; do
  echo "$u: $(systemctl is-enabled "$u" 2>/dev/null || true)"
done
cockpit.socket: disabled
avahi-daemon.service: masked
cups.service: 
bluetooth.service: masked
ModemManager.service: disabled
rpcbind.socket: 
xvnc.socket: disabled

Avahi and Bluetooth are masked by the CIS Level 1 remediation. The CUPS server (see Printing) and rpcbind are not installed at all, so the loop prints nothing after cups.service: and rpcbind.socket:. The others are disabled, so you can turn any of them back on with one command. To use the Cockpit web console, run sudo systemctl enable --now cockpit.socket, then reach it on port 9090 through an SSH tunnel (ssh -N -L 9090:localhost:9090 ...). Do not enable xvnc.socket: it serves a login screen over VNC on port 5900 with no VNC password.

Printing

This image does not include printing. The CUPS print server (cups) and its filters (cups-filters, cups-filters-libs) are not installed, and /etc/dnf/dnf.conf excludes them so that no update or dependency brings them back. The CUPS client library (cups-libs) stays, because GTK and GNOME Settings link against it.

rpm -q cups cups-filters cups-libs | sed 's/-[0-9].*//'
grep '^exclude=' /etc/dnf/dnf.conf
package cups is not installed
package cups-filters is not installed
cups-libs
exclude=cups cups-filters cups-filters-libs cups-browsed libcupsfilters libppd

If you need printing, remove the exclude= line from /etc/dnf/dnf.conf, then run sudo dnf install -y cups cups-filters and sudo systemctl enable --now cups.

Patching

dnf-automatic installs security updates automatically each day. Check what is pending now:

sudo dnf -q check-update --refresh >/dev/null; echo "dnf check-update exit code: $? (0 = nothing to update, 100 = updates available)"
grep -E '^(upgrade_type|apply_updates)' /etc/dnf/automatic.conf
systemctl is-enabled dnf-automatic.timer
dnf check-update exit code: 0 (0 = nothing to update, 100 = updates available)
upgrade_type = security
apply_updates = yes
enabled

An instance launched later lists the updates released since, with exit code 100. To install everything now, run sudo dnf -y upgrade, then sudo systemctl reboot if a new kernel was installed. Automatic updates never restart the instance for you; dnf needs-restarting -r tells you whether a restart is owed.

Troubleshooting

"Connection refused" on localhost:5901

VNC is switched off on a new instance by design. Run sudo cloudimg-vnc enable first, and check that the SSH tunnel is still running in its own terminal window. sudo cloudimg-vnc status shows whether anything is listening on 5901.

The viewer says "Authentication failure"

The viewer wants your VNC password (set with vncpasswd), not your account password. Only its first 8 characters count. To set a new one, run vncpasswd again as yourself, then sudo cloudimg-vnc disable and sudo cloudimg-vnc enable.

Connecting to the instance's public IP on port 5901 does not work

That is by design. The VNC server listens on localhost only, whatever a user's own TigerVNC settings say. Use the SSH tunnel in Step 5.

The screen is locked and asks for a password

The screen locks after 15 minutes of inactivity (CIS Level 1). Unlock it with your account password (the one from sudo passwd). After 5 failed attempts the account is locked for 15 minutes; to unlock it early, run sudo faillock --user ec2-user --reset over SSH.

The desktop does not start for a user

sudo cloudimg-vnc enable <user> says what is missing. For anything else, read journalctl -u vncserver@:1 and the session log in ~<user>/.local/state/tigervnc/.

Cannot connect via SSH

  1. Check that port 22 is allowed in the security group from your address.
  2. Use the user ec2-user and the key pair you chose at launch. Password login over SSH is disabled by design.

A service is blocked and the logs mention SELinux

SELinux is enforcing. Read the denial with sudo ausearch -m avc -ts recent before you change anything. Fix the label or the boolean it names rather than turning SELinux off.

Emergency access

No account has a password until you set one. EC2 Serial Console and single-user recovery need a local password, and the one you set in Step 3 works for the serial console. If SSH is unavailable, Session Manager is the other way in.

Support

cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk.

AlmaLinux is free and open source. It is assembled from thousands of independently packaged components, each under its own licence (see rpm -qi <package>). No subscription or licence key is required.

AlmaLinux is a trademark of the AlmaLinux OS Foundation. GNOME is a trademark of the GNOME Foundation. TigerVNC is a trademark of its respective owner. CIS and CIS Benchmarks are trademarks of the Center for Internet Security, Inc. The names are used only to identify the software this image contains. cloudimg is not affiliated with or endorsed by the AlmaLinux OS Foundation, the GNOME Foundation, the TigerVNC project or CIS.