Three things decide which credential source you should use: whether a person is sitting at the console, whether the secret may live on disk, and whether another process must answer the prompt. Get the choice wrong and the client reports a stdin problem while the password is perfectly valid, which sends most people to the server for no reason. Pick the path first; the error messages only make sense once you know which path produced them.

The three conditions that decide it

The console prompt is the only path that needs a human, so it is the only one that fails quietly in a systemd unit or a container. If a person starts the client and types the password, no file is involved and nothing sensitive lands on disk. If the client starts at boot, that same prompt becomes a hang, and the timeout usually appears as a generic connection failure rather than a credential error.

A credential file is the right answer when the process starts unattended and the secret may survive on disk under a controlled permission set. When the secret must not be written at all - a vault-only policy, a short-lived token - you need a stream the client can open, which in practice means process substitution or a helper writing into a file descriptor. The management interface covers the fourth case: a controller that already manages the tunnel's lifecycle and can answer questions over a socket.

  • Human at the console, no automation: interactive prompt.
  • Unattended start, secret may be stored: credential file with mode 0600.
  • Unattended start, secret may not be stored: process substitution or an fd written by a secrets agent.
  • Another process owns the client: management interface.

When two answers fit, prefer the one that keeps the secret out of argv and out of shell history. A password passed as a program argument is readable by every local user through the process list, and that exposure is hard to undo.

What each input path actually does

--auth-user-pass on its own asks for a username and a password on the console. Given a filename, the client takes line one as the username and line two as the password; nothing supports quoting, comments or key-value syntax. That is the whole format, so anyone who edits it expecting an INI file breaks it. An inline <auth-user-pass> block holds the same two lines inside the profile, which makes the profile itself a secret and puts it in the same handling class as a private key.

The other two paths are a stream and a socket. OpenVPN only needs a readable file descriptor, so it will accept a process substitution, a pipe-backed /dev/fd entry, or a filename that is really a FIFO. The management interface is different in kind: the client asks a supervising process for the values instead of reading them itself. That difference matters on reconnect: a credential file, a FIFO that a secrets helper can write again, and the management interface can re-supply without a person; process substitution cannot, because the descriptor is consumed on first read.

The file has to be exactly two lines

Two failure modes cover most broken credential files. A Windows editor leaves CRLF endings, the carriage return becomes part of the password, and the server rejects a value that looks correct on screen. A blank second line - an empty line where the password belongs - leaves the client with an empty password, which the server rejects even though the value on screen looks right. A trailing newline after the password is harmless. Write the file from the shell, not from an editor, and check the bytes:

install -m 600 /dev/null /etc/openvpn/client.creds
printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS" > /etc/openvpn/client.creds
od -c /etc/openvpn/client.creds | head -3

The path matters as much as the content. A relative path resolves against the process working directory, which for a service is rarely the directory where you tested. Use an absolute path, own the file by the account that runs the client, and keep mode 0600 so group and other cannot read it. The OpenVPN 2.6 manual lists the accepted forms of the directive; check it before you invent a third one.

Test the file before you blame the server. Running openvpn --config client.ovpn --auth-user-pass /etc/openvpn/client.creds --verb 4 prints whether the file was opened and, later, whether the credentials were accepted, which tells you which layer to change next.

Streams, stdin and the errors they produce

Piping the password in is the first thing most people try and the path with the most surprises. Process substitution works because the client only needs a readable descriptor: openvpn --config client.ovpn --verb 3 --auth-user-pass <(printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS"). Passing /dev/stdin works only when stdin is still open and readable at the moment the client asks, which is not true in a pipeline that has already consumed it.

ERROR: could not read Auth username from stdin has two distinct causes. On some 2.3.x clients the message came from a stdin-handling defect that later 2.3 releases fixed, so a client that old improves only by upgrading. On a current client the same wording means the client asked for console input and stdin was not a terminal - exactly the situation in a systemd unit, a cron job, or a container started without -t.

auth-nocache adds a third loop that looks like a broken password. It drops the credentials from memory after use, so a reconnect triggered by a network change has nothing to replay and the prompt returns. That is the directive working as designed. auth-nocache changes what the client keeps in memory, not what the server or the identity provider thinks of the account, so the whole fix is re-supplying the same file, re-read on restart - and for a managed connection that supply should come from the approved secret store, not a password in a script.

The management interface when another process owns the prompt

A controller that already supervises the tunnel can also answer its questions. Start the client with --management 127.0.0.1 7505, read the state lines, and watch for >PASSWORD:Need 'Auth' username/password. The controller answers with username "Auth" alice followed by password "Auth" ... on separate command lines. The 'Auth' token echoes the identifier from the request and has to match, or the answer is ignored and the client waits.

The cost is a state machine you now own: connection ordering, client restarts, and a local socket that other users on the host must not reach. What you buy is a credential supplied at runtime, with no file on disk and no prompt that can hang. If nothing else in your stack already speaks the protocol, this path adds a component to maintain.

Failures that look like credential problems

A TLS handshake that never completes, or a VERIFY ERROR: depth=0, error=certificate signature failure, is a trust problem and happens before any username is read. A proto tcp client pointed at a UDP listener fails in the same place: the packets never reach the authentication stage, so every password looks wrong by association. Keep certificate verification on before the credential is sent; a familiar prompt says nothing about who receives the password. Check the certificate chain, the common name and the transport before you touch the credential.

Shell bugs in the script that assembles the profile are the other false lead. A missing space inside [ ! -f "$file" ] produces command not found, the script stops early, the profile is never rebuilt, and the client starts with the previous one. That error belongs to the shell, not to authentication, and no amount of retyping the password will clear it.

Not every prompt in this flow is this credential. A private-key passphrase, an HTTP proxy login and the service password are three different secrets asked for at different phases, and a key passphrase is usually handled by the client or an agent rather than by auth-user-pass. If the prompt appears before the TLS session is up, you are answering the wrong question and no account reset will help. Once the wrong prompt is excluded, the remaining checks start at the earliest layer:

  • Read the first protocol error, not the last credential line: TLS, then authentication, then push.
  • Confirm the file is two lines, has no carriage returns, and is referenced by an absolute path.
  • Confirm the profile and the command line do not disagree about the directive.
  • Separate "credentials accepted" from "traffic flows" - routing and DNS are a later layer.

Once the client reaches the push phase, stop looking at credentials and start reading the routes and DNS servers the server sent. That is the next problem, and it has nothing to do with the password you just fixed.

Allakkat attuumassuteqartut