Skip to content
xaweho

Knowledge base · advanced

SSH and headless workflows

Controlling your Mac via SSH: public-key auth, tmux sessions, Xcode builds from the command line, GitHub Actions runners. When you don't need a desktop.

advanced ·

For CI/CD workflows, automated builds and server tasks, you don’t need a screen — SSH is enough. The Mac is essentially a Unix system; everything you do on Linux over SSH works on macOS too. Here are the setup steps and typical workflows.

SSH is active by default

With us, SSH is enabled on all Mac minis. You can log in right away:

ssh user@<your-mac-ip>

Log in with the macOS user password from our setup mail.

With AnyDesk + SSH both active: they work in parallel.

Setting up public-key authentication

For secure and automated SSH logins, public-key auth is mandatory. You should disable password login afterwards.

On your computer: generate a key

If you don’t have an SSH key yet:

ssh-keygen -t ed25519 -C "macmini-xaweho"

Confirm the default path (~/.ssh/id_ed25519). Optionally set a passphrase.

Getting the public key onto the Mac

Option 1: ssh-copy-id

ssh-copy-id user@<your-mac-ip>

Enter the password → the public key gets copied to the Mac.

Option 2: manually

Copy the public key:

cat ~/.ssh/id_ed25519.pub

On the Mac (via AnyDesk, or SSH with password): append it to ~/.ssh/authorized_keys:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA... macmini-xaweho" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Test

ssh user@<your-mac-ip>

Should work without a password prompt.

Disabling password login

On the Mac as admin:

sudo nano /etc/ssh/sshd_config

Find/adjust these lines:

PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

Reload the SSH daemon:

sudo launchctl stop com.openssh.sshd
sudo launchctl start com.openssh.sshd

From now on, login is public-key only. More secure.

SSH config on your computer

In ~/.ssh/config:

Host macmini
    HostName <your-mac-ip>
    User user
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 60

Then this is all you need:

ssh macmini

tmux for persistent sessions

If you start long-running builds, you don’t want a disconnect to kill them.

Solution: tmux (ships with macOS by default).

ssh macmini
tmux new -s build
# inside the tmux session
xcodebuild -workspace ./MyApp.xcworkspace -scheme MyApp ...
# Ctrl+B, then D to detach

The SSH connection can then drop — the build keeps running. On your next connect:

ssh macmini
tmux attach -t build

You see the build progress as if nothing had happened.

Xcode from the CLI

Xcode has a CLI tool: xcodebuild.

Example build:

xcodebuild -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath ~/Builds/MyApp.xcarchive \
  archive

Fastlane users can work with fastlane directly on the Mac.

For Apple code signing, an Apple ID + developer certificate must be set up — that works only via a GUI login (AnyDesk / Screen Sharing) for the initial setup. After that, everything runs headless.

Registering a GitHub Actions runner

Self-hosted GitHub Actions runner on the Mac for iOS builds:

On GitHub

Repository → Settings → Actions → Runners → New self-hosted runner → select macOS.

GitHub gives you the commands with a token.

On the Mac via SSH

mkdir actions-runner && cd actions-runner
curl -o actions-runner-osx-arm64-2.x.x.tar.gz -L https://github.com/actions/runner/releases/download/v2.x.x/...
tar xzf actions-runner-osx-arm64-2.x.x.tar.gz
./config.sh --url https://github.com/<your-org>/<your-repo> --token <token>
./run.sh

As a background service:

sudo ./svc.sh install
sudo ./svc.sh start

The runner runs as a macOS service and picks up workflows automatically.

GitLab CI runner

Analogous for GitLab:

brew install gitlab-runner
gitlab-runner register
# enter the server URL and token from GitLab
gitlab-runner run

As a background service:

gitlab-runner install
gitlab-runner start

Other CI/CD platforms

  • Bitrise: has its own setup guide for self-hosted Mac runners
  • CircleCI: cloud-only for macOS, no self-hosted
  • Buildkite: very good for Mac self-hosting, simple setup

File transfer via SCP / SFTP

# download a file from the Mac
scp macmini:~/Builds/MyApp.ipa ./

# upload a file to the Mac
scp ./local-file.txt macmini:~/

# recursive sync
rsync -avz ./local-folder/ macmini:~/Projects/

For regular syncing: rsync with --delete for a 1:1 mirror.

Tunneling: reaching web UIs on the Mac

If a tool on the Mac has a web UI on port 8080 but shouldn’t be publicly reachable:

ssh -L 8080:localhost:8080 macmini

Then, in the browser on your computer → http://localhost:8080 → you see the Mac’s web UI through the SSH tunnel.

Handy for:

  • Selenium hub
  • Test servers
  • Local dev tools

SSH multiplexing

If you frequently open SSH connections to the same Mac, multiplexing speeds up connecting:

In ~/.ssh/config:

Host macmini
    ControlMaster auto
    ControlPath ~/.ssh/cm-%r@%h:%p
    ControlPersist 10m

The first connection takes the usual time; further ones within 10 minutes are near instant.

SSH keys on multiple devices

If you want to reach the mini from your Mac AND your iPad: a separate SSH key per device.

In ~/.ssh/authorized_keys on the mini:

ssh-ed25519 AAAA... mike-macbook
ssh-ed25519 AAAA... mike-ipad
ssh-ed25519 AAAA... mike-buildserver

If a device is lost: remove that single key.

Mosh as an SSH alternative

Mosh is a UDP-based SSH variant, more robust on shaky connections (cellular, roaming):

brew install mosh
mosh user@<your-mac-ip>

Mosh must be installed on the Mac side — open a ticket if needed.

Advantage: the connection survives short internet dropouts, reconnects are invisible.

SSH timeout configuration

To automatically disconnect idle SSH sessions:

In /etc/ssh/sshd_config on the Mac:

ClientAliveInterval 300
ClientAliveCountMax 3

After 15 minutes of inactivity, the server disconnects.

For keep-alives on your side (prevents disconnects during long idle periods):

Host *
    ServerAliveInterval 60

Typical pitfalls

”Permission denied (publickey)” despite a correctly placed key

  • File permissions on the Mac: ~/.ssh must be 700, authorized_keys must be 600
  • Key format correct? ssh-ed25519 AAAA... without a line break

SSH extremely slow when connecting

  • Disable DNS reverse lookup in /etc/ssh/sshd_config:
    UseDNS no
  • Then reload the SSH service

xcodebuild over SSH doesn’t work — “Failed to authorize …”

Apple needs a “GUI session” for some code-signing operations. Workaround:

  • Log in once via AnyDesk, configure the Apple ID + developer certificate
  • After that, xcodebuild runs headless

”Operation not permitted” on file access

macOS has System Integrity Protection and the App Sandbox. Some paths are protected.

Solution: via AnyDesk → System Settings → Privacy & Security → give SSH and Terminal Full Disk Access.

Frequently asked questions

Can I use an SSH tunnel as a VPN substitute? In a limited way. SSH dynamic port forwarding (ssh -D 1080) gives you a SOCKS proxy. For real VPN functionality, better use WireGuard.

How many parallel SSH sessions? macOS default: 10. Can be raised via MaxSessions in sshd_config.

Viewing SSH logs on the Mac? log stream --predicate 'process == "sshd"' shows SSH login attempts live, log show --last 1h --predicate 'process == "sshd"' retroactively for the last hour. The old /var/log/system.log has been empty since macOS 10.12 — macOS logs via Unified Logging.

Restarting the Mac via SSH?

sudo shutdown -r now

Can I move the SSH port from 22 to something else? Set Port 2222 in /etc/ssh/sshd_config, then connect with ssh -p 2222 .... Minimal security effect — the better solution: public-key auth + Fail2Ban.

Fail2Ban on macOS? On request, we enable an equivalent. Open a ticket — we configure the pf firewall with brute-force protection.

Is SSH agent forwarding safe? Use with caution. With ForwardAgent yes, an attacker on the Mac could access your local SSH keys. Default: off.

WoL — starting the Mac via Wake-on-LAN? The Mac mini supports Wake-on-LAN. We can set up WoL for you — open a ticket if needed.

What’s next

Related products
Tags
macmini ssh headless ci-cd

Did this article help?

If not, open a ticket. If it did, we're happy about a referral — both sides get €25 credit on their customer account.