The script exits zero, the tunnel appears, and the next run is refused before it ever asks for a password. Or the same command works in a terminal and fails from a scheduled job with an event-not-found error that names nothing useful. Both are the AnyConnect client's stdin conversation going wrong rather than a network fault. Treat the login as a scripted question-and-answer and the failures become predictable and small.

Why the Same Command Fails Inside a Script

The client reads its answers from standard input, one line per prompt, in the order the gateway asks them. Anything that changes the number of prompts, such as a new banner, a group selector or a second-factor challenge, shifts every later answer by one line. The client then reads a username where it expected a password and reports a failure that points at neither.

A password containing an exclamation mark adds a quieter second failure. Inside double quotes the shell expands it long before the client sees anything.

Quote the payload with single quotes and the character travels intact. That is the entire fix, and it explains a whole class of reports where the same credential works when typed and fails when stored. Check what the shell did before you check the gateway: print the payload through a wrapper that shows the exact bytes and count the lines. Most of these investigations end right there.

Write the Answers in the Order the Client Asks

Run the client interactively once and write down every prompt in sequence. A group selector appears only when the gateway publishes several profiles, and a banner prompt appears only when one is configured. The answer file has to match the gateway you are actually pointed at, not the one in the documentation.

Then build the file with that exact sequence, one answer per line. The group index is the number the client lists rather than a profile's position in a document. Accept the banner only if your gateway sends one, and add a line for every extra question that gateway is known to ask. Start the client with the non-interactive flag so it consumes the file instead of waiting for a terminal.

connect vpn.lab.example
0
username
password
y
exit
/opt/cisco/anyconnect/bin/vpn -s < anyconnect.txt

The same sequence can arrive through a pipe when you would rather not keep a file on disk. The quoting rules still decide whether it survives.

Keep the Credential Out of the Process List

An argument on the command line is readable by every local user for the life of the process, so the password belongs in a file or a pipe and never in the argument list. Make that file readable only by the account that runs the script, and delete it when the job finishes. On macOS the binary usually sits at the same path as on Linux, but confirm the installed location rather than assuming it. A wrapper that probes both paths is shorter than the incident write-up.

printf 'username\npassword\ny\n' | /opt/cisco/anyconnect/bin/vpn -s connect vpn.lab.example

Do not let the script retry forever either. A changed banner or a password that expired overnight converts a fixed answer file into a retry loop, and the retry loop is what locks the account out.

Fail once, print the client's own error text, and exit. A scheduled job needs the same identity and credential-availability review as any other unattended task, because a credential that expires quietly is indistinguishable from an outage at three in the morning.

Confirm the Session Before You Trust the Exit Code

A connected state proves the tunnel exists and nothing beyond that. Test one resource that must be reachable through it before the script starts real work, and treat a failure there as a different fault from a failed login. Otherwise you will retry a perfectly good connection that simply cannot reach its destination.

Ask the client for its state before and after the disconnect, because a stale session can refuse the next connect while your script reports success.

/opt/cisco/anyconnect/bin/vpn state
/opt/cisco/anyconnect/bin/vpn disconnect

Record the state output next to the exit code, since a code alone cannot separate a refused login from a session that never closed. When the gateway demands a second factor or an interactive approval, stop scripting that step and use the client's supported mechanism instead. A fixed answer file is a convenience for a credential you control, not a way around an authentication step you do not. Keep the scope that narrow and this stays a short script.

When a Fixed Answer File Is the Wrong Tool

A second factor or an approval prompt defeats the pattern entirely, and that is a design boundary rather than a scripting puzzle. Use whatever mechanism the client supports for that step instead of guessing the answer in advance. A fixed file only works for credentials that stay the same between runs.

Two older credential styles also get mistaken for AnyConnect. A vpnc profile speaks a different protocol, and it wants the group name as an IPSec identifier, then a shared group secret, then your own account details.

Because the protocols differ, vpnc cannot reach a gateway that publishes only AnyConnect profiles, and the usual open alternative for that service is openconnect with its anyconnect protocol selected. A pcf file belongs to the legacy Cisco client, and although its host, group name and group password can be re-entered in a client that still speaks Cisco IPsec, the file does not carry across clients. An unusual port or a non-standard tunnelling mode is generally lost in that move. Settle which protocol the gateway offers before picking a tool.

مۇناسىۋەتلىك ماقالىلەر