Scripts that need the VPN up before a backup often launch openvpn directly. That abandons the profile, DNS, and route handling that Tunnelblick already manages. The supported path is Tunnelblick's own AppleScript interface, which accepts connect, state, and disconnect requests for a named configuration. Use it, and let the application stay in charge.

Talk to the app, not to openvpn

Tunnelblick owns the OpenVPN profile, the engine selection, the scripts, and the DNS and route integration. A separate openvpn binary is a different launch arrangement, not a shortcut to the same connection.

Calling the application's scripting interface keeps that ownership intact and makes each request reviewable.

The examples here assume Tunnelblick lives in Applications, a working configuration is named HQ, and the commands run in the logged-in Mac user's session. Change the name to the exact configuration you have.

Start exactly one named profile

A connect request names the configuration. It is not a request to connect whatever was last selected.

Run it in the user session that owns the configuration:

osascript -e 'tell application "/Applications/Tunnelblick.app" to connect "HQ"'

Walk through the usual sign-in prompts. macOS may ask whether the calling app is allowed to control Tunnelblick; check which app and which target before you approve. Automation that clicks through that dialog defeats the point of the dialog.

A connect request returns before the tunnel is up

Tunnelblick's scripting guide is explicit: connect returns before the connection finishes. A zero exit status means the request was accepted, not that traffic flows.

Read the state separately:

osascript -e 'tell application "/Applications/Tunnelblick.app" to get state of first configuration where name = "HQ"'

CONNECTED is a connection-state result. It still does not prove that the private resource your job needs is reachable, so check that separately before starting dependent work.

Other controls in the scripting interface cover broader actions and are not needed when only one profile changes.

Wait with a deadline, then fail

Polling forever is how a script hangs overnight and blocks the next run. Give the wait a deadline, a short interval, and an explicit failure outcome. Treat every state other than CONNECTED as not ready, including the transitional ones. Log the last state you saw so the failure explains itself.

A bounded loop looks like this:

for i in $(seq 1 30); do
  state=$(osascript -e 'tell application "/Applications/Tunnelblick.app" to get state of first configuration where name = "HQ"')
  if [ "$state" = "CONNECTED" ]; then exit 0; fi
  sleep 1
done
echo "tunnel did not reach CONNECTED" >&2
exit 1

Thirty seconds is an example, not a universal timeout. Choose a deadline from the authentication path the profile actually uses, and keep it in the workflow record.

Check state and disconnect by name

To stop the same profile, call disconnect with the same name. That keeps the request narrow and leaves other configurations alone.

osascript -e 'tell application "/Applications/Tunnelblick.app" to disconnect "HQ"'

Keep the profile name and the controller together in the workflow's record, along with the expected authentication and completion checks. That record is what makes the command reviewable six months later.

Computer-start behavior is a different mode. Use Tunnelblick's documented startup option and the credential setup it expects. A command run in a logged-in desktop session and a service that starts with the machine do not share a context. Do not assume a script that works while you are logged in will run at boot. Test the startup path on its own, with the same deadline and logging.

פֿאַרבונדענע אַרטיקלען