Sometimes you want a headless Mac: no monitor, no keyboard, no mouse, sitting in a corner or in a rack and still behaving itself. Three things have to be true. It boots again when the power comes back, you have a reliable way in, and it logs itself in at startup, because the services you depend on are not running until someone is logged in. Hermes is the machine I did this for, and the same setup fits any other Mac.

I have done this on macOS 26 and macOS 27.

Most of it is shell commands. A few steps are not: the HDMI EDID plug goes in the port by hand, the password prompts are you typing, and when a shared drive wants a dialog clicked, that click has to happen on the desktop over Screen Sharing. Where a command has a matching control in System Settings, I name it.

Worth saying up front: this trades physical security for availability. FileVault comes off, the machine logs itself in, and the screen never locks, so anyone who walks away with the box has the disk and a live session. That is the right trade for a machine sitting in my house and the wrong one for anything that leaves it.

Power

An HDMI EDID plug stays in the HDMI port. macOS treats it as a display, so the desktop keeps a normal resolution and Screen Sharing has a real picture to show. You install that one yourself.

Then keep the machine awake, display included. sleep and displaysleep are in minutes, and 0 means never.

sh
sudo pmset -a displaysleep 0 sleep 0

pmset -g custom should show both at 0. The same choice lives in System Settings under Energy: the display does not turn off, and the Mac does not sleep. Be clear about what this buys you, though. sleep 0 keeps a listener that is already running; it does not start one that has quit.

Coming back after a power loss takes two more settings, and pmset -g should show both as 1.

sh
sudo pmset -a autorestart 1
sudo pmset -a autorestartatconnect 1

autorestart turns the Mac on after an unexpected power loss, and a shutdown you chose does not count as one. autorestartatconnect turns it on whenever power is connected, so it covers that deliberate shutdown too. In Energy, the second one reads “Start up when power is connected,” set to Always. It arrived in macOS 26.5 and wants a Mac mini from 2024 or later, a Mac Studio from 2025 or later, or an iMac from 2024 or later. If pmset rejects the key, this Mac does not have that menu, and autorestart is still the power-failure switch. After a shutdown, leave the cord out for about 30 seconds before you plug it back in, so the supply can discharge.

Ways in

Remote Login gets you a shell. Screen Sharing gets you the desktop. Both are under System Settings, General, Sharing, and both can be turned on from the shell.

Remote Login listens on TCP 22.

sh
sudo systemsetup -setremotelogin on
sudo systemsetup -getremotelogin
sudo lsof -nP -iTCP:22 -sTCP:LISTEN

lsof should show launchd or sshd, since launchd holds the port and starts sshd when you connect. systemsetup needs Full Disk Access, which is a switch you turn on yourself under Privacy & Security. If Terminal does not have it, use launchctl instead; “Already bootstrapped” means the service is already on.

sh
sudo launchctl enable system/com.openssh.sshd
sudo launchctl bootstrap system /System/Library/LaunchDaemons/ssh.plist

Screen Sharing listens on TCP 5900.

sh
sudo launchctl enable system/com.apple.screensharing
sudo launchctl bootstrap system /System/Library/LaunchDaemons/com.apple.screensharing.plist
sudo lsof -nP -iTCP:5900 -sTCP:LISTEN

The launch daemon asks for vnc-server, which is port 5900. lsof shows launchd until a client connects, then screensharingd. Remote Management is the other Sharing switch that takes 5900, and only one of the two can be on at a time, so if Remote Management is already running you have your listener.

The picture you get is the EDID desktop, and you want it unlocked. Display sleep and the screen lock are separate timers, so displaysleep 0 keeps the display powered while the lock still fires on its own schedule and hands you a password prompt instead of the desktop.

sh
sysadminctl -screenLock off -password -
sysadminctl -screenLock status

off means never, and status reports the delay in seconds when there is one. The bare - prompts for the password rather than leaving it in your shell history. There is no sudo on these two, because the screen lock belongs to the user running the command, unlike the sysadminctl -autologin call further down. In System Settings this is Lock Screen, “Require password after screen saver begins or display is turned off,” set to Never.

The screen saver is a third timer and keeps running regardless. With the lock off it is only a picture, so moving the mouse clears it, and the Never option in Lock Screen stops it starting at all.

One account

Use one account, and let it log in by itself at boot. That is what gets your services going. Do not add a second.

Automatic login will not stay on while FileVault is on, so turn FileVault off and wait for it to finish; decryption continues in the background. In System Settings this is Privacy & Security, FileVault. From the shell:

sh
fdesetup status
sudo fdesetup disable

fdesetup status should say FileVault is off before you continue. The disable command asks for your password.

Then turn automatic login on for that one account. In System Settings this is Users & Groups, “Automatically log in as.” From the shell, USER is the account’s short name, and the bare - makes sysadminctl prompt for the password so it never lands in your shell history.

sh
sudo sysadminctl -autologin set -userName USER -password -
sudo sysadminctl -autologin status

One limit worth knowing: an account that signs in with an Apple Account password cannot be the automatic login. Once this is set, a boot opens that user’s session with the login keychain unlocked.

Keys only

This part is all files. Nothing in System Settings.

Put your public key in ~/.ssh/authorized_keys before you turn passwords off. ~/.ssh is mode 700, and the key file is mode 600.

macOS turns PAM on in /etc/ssh/sshd_config.d/100-macos.conf, and with PAM on the account password keeps working until both password and keyboard-interactive logins are refused. OpenSSH keeps the first value it reads for an option, so the file below has to sort ahead of 100-macos.conf.

Write 10-pubkey-only.conf in the current directory:

text
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey

sshd reads that file only when root owns it and nobody else can write it, and a symlink into your home directory fails the check. Copy it into place:

sh
sudo install -o root -g wheel -m 644 \
  10-pubkey-only.conf \
  /etc/ssh/sshd_config.d/10-pubkey-only.conf
sudo sshd -t

Each new connection reads the file, while the session you already have stays open, so you can undo a mistake from there. From the other machine, open a second session and confirm it asks for the key before you close the first one.

sh
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|authenticationmethods) '

passwordauthentication and kbdinteractiveauthentication should be no, pubkeyauthentication should be yes, and authenticationmethods should be publickey.

Shared drives

A share mounted in Finder drops when the network drops, and again after a restart. Asking Finder to mount it again is a script. Clicking whatever macOS asks for next is not.

In the script below, name is the folder under /Volumes and url is the server and the share. The check looks for the ( that macOS writes after the path, so a volume named Share is not confused with Share Backup.

sh
#!/bin/sh
set -eu

name="Share"
url="smb://files.example/Share"

if mount | grep -F " on /Volumes/${name} (" >/dev/null; then
  exit 0
fi

open "$url"

open returns as soon as Finder accepts the request, which does not mean the volume mounted. Run mount again. If the volume is still missing, there is a dialog sitting on the desktop, either the connect sheet, a keychain prompt, or a certificate warning, and none of it surfaces in the SSH session. Open Screen Sharing on port 5900 and click it.

A LaunchAgent can run the script at login. It still cannot click the dialog.

What is listening

sh
sudo lsof -nP -iTCP -sTCP:LISTEN

Every service you expect to reach should be on that list. sleep 0 is what keeps those listeners through a long idle; it will not bring a dead one back.