The scenario
You just provisioned a new VPS or repurposed an old desktop into a dedicated file server. The machine boots straight into GNOME or KDE. You sit in front of it for two minutes, realize you will never touch the monitor again, and want to reclaim the RAM and CPU cycles the desktop environment is burning. You need a clean, text-only boot that drops you at a login prompt and accepts SSH connections from your workstation.
What actually changes when you go headless
Fedora ships with a graphical target by default. That target tells systemd to start a display manager, load the X11 or Wayland compositor, and launch the desktop shell. When you switch to a headless configuration, you are not uninstalling the desktop. You are changing the default systemd target to multi-user.target. This target stops the graphical stack and starts only the core services: networking, SSH, cron, and your custom daemons.
Think of it like switching a car from automatic to manual. The engine and transmission are still there. You are just telling the system which gear to engage at startup. The graphical packages remain on disk in case you ever need to reattach a monitor or run a GUI tool temporarily. The change happens in the systemd symlink chain and the firewall rules.
Systemd uses targets as synchronization points. graphical.target inherits from multi-user.target and adds the display manager dependency. When you set multi-user.target as default, systemd skips the graphical inheritance chain entirely. The kernel still loads, initramfs still mounts the root filesystem, and the console still appears. You just lose the greeter and the window manager.
The standard conversion procedure
Open a terminal on the machine. If you are already logged in locally, run these commands as root or with sudo. If you are working remotely, keep your current SSH session open while you test the changes in a second window. A misconfigured firewall or disabled SSH daemon will lock you out.
First, ensure the server base packages are present. Fedora separates desktop and server components into package groups. The Server with GUI group pulls in the core services, but we will exclude the desktop environments to keep the system lean.
sudo dnf groupinstall "Server with GUI" --setasdefault --exclude='*GNOME*' --exclude='*KDE*' -y
# --setasdefault marks the group as the primary environment for future dnf group commands
# --exclude prevents the package manager from pulling in heavy desktop shells
# -y skips the confirmation prompt for automated or scripted runs
Next, change the boot target. This creates a symlink in /etc/systemd/system/default.target pointing to the text console.
sudo systemctl set-default multi-user.target
# Replaces the graphical.target symlink with multi-user.target
# Takes effect on the next boot, not the current session
# Does not stop running graphical processes in the current login
Disable the display manager so it does not consume resources or try to start on the next boot. Fedora uses GDM by default, but the command works for SDDM or LightDM if you installed them.
sudo systemctl disable --now gdm.service
# --now stops the running service immediately and disables it for future boots
# Frees roughly 400 to 800 megabytes of RAM depending on your hardware
# Leaves the package installed so you can re-enable it later if needed
Enable the SSH daemon. Fedora ships with sshd but leaves it disabled by default for security.
sudo systemctl enable --now sshd.service
# Creates the systemd unit symlinks and starts the daemon in the current session
# Listens on port 22 by default unless /etc/ssh/sshd_config specifies otherwise
# Logs authentication attempts to /var/log/secure and journalctl
Open the firewall. Fedora uses firewalld with a zone-based architecture. The default zone is usually public, which blocks inbound connections except for DHCPv6-client and mdns.
sudo firewall-cmd --permanent --add-service=ssh
# Adds the SSH service definition to the persistent configuration file
# --permanent writes to /etc/firewalld/zones/public.xml instead of the runtime kernel tables
# Runtime and persistent configs diverge if you skip the reload step
sudo firewall-cmd --reload
# Applies the persistent rules to the live kernel firewall without dropping existing connections
# Always run this after every rule change to keep both configs in sync
Reboot the machine to verify the target switch. The system should drop you at a login: prompt instead of the graphical greeter.
Verify the transition
After the reboot, log in locally or via SSH. Run a quick check to confirm systemd is running the correct target and that the graphical stack is inactive.
systemctl get-default
# Returns multi-user.target if the symlink was created correctly
# Fails silently if the default.target file is missing or corrupted
systemctl status gdm.service
# Should show inactive (dead) and disabled
# Active (exited) means it ran once but stopped, which is normal after disable
systemctl status sshd.service
# Should show active (running) and enabled
# Check the Main PID line to confirm the daemon is actually listening
Check the active network listeners to make sure SSH is bound to the correct interface.
ss -tlnp | grep sshd
# Lists TCP listening sockets and filters for the sshd process
# You should see 0.0.0.0:22 or [::]:22 depending on your IPv4/IPv6 setup
# Empty output means sshd failed to bind or the firewall is dropping the probe
Run journalctl -xe if anything looks wrong. The x flag adds explanatory hints from systemd documentation and the e flag jumps to the end of the log. Most boot failures show up in the last twenty lines. Read the actual error before guessing.
Common pitfalls and error patterns
The most common mistake is disabling SSH before testing the connection. If you run systemctl disable sshd and then close your terminal, you will need physical console access or a hypervisor rescue shell to recover. Always keep a second session open or use a watchdog timer for critical changes.
Another frequent issue involves SELinux. If you change the SSH port in /etc/ssh/sshd_config, firewalld will reject the traffic and SELinux will block the daemon from binding to the new port. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. You can restore the default context with semanage port -a -t ssh_port_t -p tcp <new_port> and reload firewalld.
NetworkManager sometimes drops interfaces when the desktop environment stops. If your server uses a static IP configured through the GNOME network settings, the configuration lives in /etc/NetworkManager/system-connections/. The files are owned by root and have strict permissions. Edit them with nmcli or nmtui instead of raw text editors. The nmtui command provides a terminal-based interface that writes the correct XML structure and triggers the daemon to reload.
Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/. Package updates will overwrite /usr/lib/ and silently revert your changes.
If you see [FAILED] Failed to start sshd.service during boot, your SSH configuration probably contains a syntax error or references a missing host key. The daemon refuses to start with broken configs. Run sshd -t to validate the configuration file before restarting. The command prints sshd: error in configuration file /etc/ssh/sshd_config line 42: Unsupported option PermitRootLogin when it catches a typo. Fix the line and restart the unit.
If the boot menu is gone and the system hangs at a black screen, the display manager might be fighting with a broken GPU driver. Boot into the recovery entry from GRUB, switch to multi-user.target, and reinstall the firmware packages. Trust the package manager. Manual file edits drift, snapshots stay.
Fedora's release cadence is six months. The N-2 release goes end-of-life when N+1 ships. Plan upgrades on that cycle. Run dnf upgrade --refresh weekly to pull security patches. Use dnf system-upgrade only when crossing major releases. They are different commands with different risk profiles.
Choose the right Fedora variant
Use Fedora Server when you are building a dedicated headless machine from scratch. Use Fedora Workstation when you need a development machine that occasionally runs GUI tools but mostly serves as a remote server. Use Silverblue when you want a known-good base image you can always roll back to with rpm-ostree. Stay on the upstream Workstation if you only deviate from the defaults occasionally and prefer the standard dnf package flow.