
Short answer: install two launchd system daemons with StartCalendarInterval arrays — one that loads Apple's Screen Sharing daemon at 09:00 Mon–Fri, one that unloads it at 18:00 — and test both with launchctl kickstart -k the same day. The complete plists and the test procedure are below. On Figment the task fits a shared physical Mac where a scheduled off-hours lockdown is the point: the i3 at $49/month and M-series from $95/month are configured on request, so ask for a user that can write to /Library/LaunchDaemons/ when you submit the configuration. The $29 virtual Intel macOS VM can also host these jobs inside its browser-console desktop terminal; confirm the exact access path in your quote before you rely on this schedule as a security boundary.
If you rent a Mac for a team of contractors, leaving VNC open around the clock creates two problems: unplanned evening or weekend work that bypasses your approval process, and a larger attack surface for credential-stuffing against VNC passwords. Scheduling Screen Sharing to business hours takes about fifteen minutes to set up and enforces the boundary automatically.
This guide produces two launchd daemon plist files — one to enable Screen Sharing at 09:00 and one to disable it at 18:00, Monday through Friday — plus a test procedure you can run on the same day without waiting for the clock to advance.
Screen Sharing being disabled means no new VNC connections can be established. Any contractor who attempts to connect outside business hours receives an immediate "unable to connect" response rather than a login prompt. Combined with a strong VNC password, this reduces the credential-brute-force window from 168 hours per week to roughly 45. It also makes your activity log easier to review: any connection landing inside the Screen Sharing subsystem outside the scheduled window is anomalous by definition.
For rental cost management, contractors who cannot connect off-hours cannot accidentally leave long-running CPU jobs running overnight — a practical guard on a shared compute budget.
Screen Sharing on macOS is a system LaunchDaemon (com.apple.screensharing) with a plist at /System/Library/LaunchDaemons/com.apple.screensharing.plist. Apple documents turning it on and off in Turn screen sharing on or off on Mac. From the Terminal:
# Enable Screen Sharing
sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.screensharing.plist
# Disable Screen Sharing
sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.screensharing.plist
On macOS 13 Ventura and later Apple prefers the domain-aware form:
sudo launchctl bootstrap system /System/Library/LaunchDaemons/com.apple.screensharing.plist
sudo launchctl bootout system/com.apple.screensharing
Both forms still work on current macOS; use whichever matches the version installed on your rental. Starting with macOS 12.1 Apple also tightened TCC gating around Screen Sharing — on some configurations launchctl only cleanly starts the daemon after the service has been enabled once via System Settings → General → Sharing → Screen Sharing. If your scheduled jobs refuse to start it, flip the toggle on once by hand to grant the one-time permission, then flip it off and let the daemon take over.
Verify the current state:
sudo launchctl list | grep screensharing
# A line with a PID means the daemon is running.
# A line with "-" means it is loaded but not currently running.
# No line at all means it is not loaded.
StartCalendarInterval is a dict (or array of dicts) with keys Minute, Hour, Day, Weekday, and Month; omitted keys act as wildcards, documented in Apple's Creating launchd Jobs and the launchd.plist(5) man page. Weekday is 0–7, where 0 and 7 both mean Sunday, so Monday–Friday is 1 through 5. A range of weekdays needs one dict per day.
The two plists below belong in /Library/LaunchDaemons/ and run as root, which is required for launchctl calls that start or stop a system service.
/Library/LaunchDaemons/com.company.screensharing-enable.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.company.screensharing-enable</string>
<key>ProgramArguments</key>
<array>
<string>/bin/launchctl</string>
<string>load</string>
<string>-w</string>
<string>/System/Library/LaunchDaemons/com.apple.screensharing.plist</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>1</integer></dict>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>2</integer></dict>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>3</integer></dict>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>4</integer></dict>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>5</integer></dict>
</array>
<key>StandardOutPath</key>
<string>/var/log/screensharing-schedule.log</string>
<key>StandardErrorPath</key>
<string>/var/log/screensharing-schedule.log</string>
</dict>
</plist>
/Library/LaunchDaemons/com.company.screensharing-disable.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.company.screensharing-disable</string>
<key>ProgramArguments</key>
<array>
<string>/bin/launchctl</string>
<string>unload</string>
<string>-w</string>
<string>/System/Library/LaunchDaemons/com.apple.screensharing.plist</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>1</integer></dict>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>2</integer></dict>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>3</integer></dict>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>4</integer></dict>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer><key>Weekday</key><integer>5</integer></dict>
</array>
<key>StandardOutPath</key>
<string>/var/log/screensharing-schedule.log</string>
<key>StandardErrorPath</key>
<string>/var/log/screensharing-schedule.log</string>
</dict>
</plist>
Set ownership and permissions before loading (LaunchDaemons must be root-owned and not group/world-writable or launchd will refuse them):
sudo chown root:wheel /Library/LaunchDaemons/com.company.screensharing-*.plist
sudo chmod 644 /Library/LaunchDaemons/com.company.screensharing-*.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.company.screensharing-enable.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.company.screensharing-disable.plist
launchd fires calendar jobs in the system timezone. If your Mac is in a US data centre but the people using it are in Europe, 09:00 and 18:00 mean the Mac's local clock, not theirs. Set the timezone first:
# Example: UK business hours
sudo systemsetup -settimezone "Europe/London"
sudo systemsetup -gettimezone
sudo systemsetup -listtimezones prints the valid identifiers. After changing the timezone, bootout and bootstrap both plists again so launchd recalculates the next fire time.
You do not need to advance the system clock. launchctl kickstart runs a loaded job immediately; -k kills a running instance first, which is fine here because each job is a short-lived command.
# Run the disable job now
sudo launchctl kickstart -k system/com.company.screensharing-disable
sleep 2
sudo launchctl list | grep screensharing
# Expect: "-" (loaded, not running) or no line at all
# Run the enable job now
sudo launchctl kickstart -k system/com.company.screensharing-enable
sleep 2
sudo launchctl list | grep screensharing
# Expect: a line with a PID
Then test from an actual VNC client at each state. A refused connection while the daemon is stopped is the behaviour you want.
Manual override for emergencies. If someone needs access outside hours, enable it by hand and log the event so the record stays honest:
sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.screensharing.plist
printf '%s MANUAL_OVERRIDE_ENABLE by=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$USER" \
| sudo tee -a /var/log/screensharing-schedule.log >/dev/null
# When done
sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.screensharing.plist
printf '%s MANUAL_OVERRIDE_DISABLE by=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$USER" \
| sudo tee -a /var/log/screensharing-schedule.log >/dev/null
The scheduled jobs re-assert the correct state at the next fire time, so an override cannot silently persist into the next morning.
The plists write their own output to /var/log/screensharing-schedule.log. To capture connection events, stream the Screen Sharing subsystem to a review log:
sudo log stream --predicate 'subsystem == "com.apple.screensharing"' \
--style compact >> /var/log/screensharing-attempts.log &
Attempts that arrive while the daemon is stopped never reach the Screen Sharing subsystem; they land in the network layer. For a simple confirm-the-schedule-ran check, add a third launchd job that runs netstat -an | grep '\.5900 .*LISTEN' every 15 minutes and writes a line with the result. If port 5900 is listening at 02:00, the disable job did not fire and the log makes that obvious.
Pick the plan by the job the schedule protects.
launchctl commands above. No GPU or audio, and ARM-only execution needs an M-series.When you request the configuration, tell us you need a user that can write /Library/LaunchDaemons/ and run sudo launchctl bootstrap system ... so these daemons load on first boot.
Start here: /mac-hosting. Configure a plan: /mac-hosting/start.
Related reading