How to use client certificates with wget

Mutual TLS is common on internal APIs, private package feeds, and regulated download endpoints where a password prompt is not acceptable. Presenting a client certificate from wget keeps those transfers scriptable while still binding access to a revocable machine or service identity.

During the TLS handshake, wget can present a client certificate with --certificate and the matching private key with --private-key, while --ca-certificate points to the CA bundle that validates the server. Current GNU wget still treats PEM as the default format, with --certificate-type and --private-key-type available when the files are stored as DER instead.

Client-certificate workflows fail most often because the key does not match the certificate, the server CA chain is incomplete, or the files are readable by other accounts. Keep certificate material in a private directory, test the handshake with spider mode before pulling a large payload, and persist defaults in ~/.wgetrc only when the same identity is reused regularly.

Steps to use client certificates with wget:

  1. Confirm that the installed wget build exposes the client-certificate options needed for mutual TLS.
    $ wget --help | grep -E -- '--certificate=FILE|--private-key=FILE|--ca-certificate=FILE'
           --certificate=FILE          client certificate file
           --private-key=FILE          private key file
           --ca-certificate=FILE       file with the bundle of CAs

    Current GNU wget still accepts PEM by default; add --certificate-type=DER or --private-key-type=DER only when the files are not in PEM format.

  2. Export PEM files only when the client identity arrives as a .p12 or .pfx bundle instead of separate certificate and key files.
    $ openssl pkcs12 -in client.p12 -clcerts -nokeys -out client-cert.pem
    $ openssl pkcs12 -in client.p12 -nocerts -nodes -out client-key.pem

    The second command writes the private key without passphrase protection so non-interactive jobs can use it; keep that file in a restricted directory.

  3. Stage the certificate, private key, and CA bundle in a restricted directory and lock down the file permissions before the first request.
    $ install -d -m 700 "$HOME/.local/share/wget/mtls"
    $ install -m 600 client-cert.pem "$HOME/.local/share/wget/mtls/client-cert.pem"
    $ install -m 600 client-key.pem "$HOME/.local/share/wget/mtls/client-key.pem"
    $ install -m 600 ca-chain.pem "$HOME/.local/share/wget/mtls/ca-chain.pem"
    $ ls -l "$HOME/.local/share/wget/mtls"
    total 24
    -rw-------  1 user user 1946 Mar 27 06:55 ca-chain.pem
    -rw-------  1 user user 1824 Mar 27 06:55 client-cert.pem
    -rw-------  1 user user 3272 Mar 27 06:55 client-key.pem

    Readable private keys let any local user impersonate the client identity for the protected service.

  4. Inspect the client certificate metadata before presenting it to the remote service.
    $ openssl x509 -in "$HOME/.local/share/wget/mtls/client-cert.pem" -noout -subject -issuer -enddate
    subject=CN = build-agent-01.internal.example
    issuer=CN = Internal Issuing CA
    notAfter=Dec 12 06:15:01 2029 GMT

    Confirm the subject, issuing CA, and expiry against the record from the PKI team before the first live connection.

  5. Validate the handshake first with spider mode so certificate and trust errors appear before a full download starts.
    $ wget --spider \
      --certificate="$HOME/.local/share/wget/mtls/client-cert.pem" \
      --private-key="$HOME/.local/share/wget/mtls/client-key.pem" \
      --ca-certificate="$HOME/.local/share/wget/mtls/ca-chain.pem" \
      https://mtls.internal.example/reports/nightly.csv
    Spider mode enabled. Check if remote file exists.
    --2026-03-27 06:55:21--  https://mtls.internal.example/reports/nightly.csv
    Resolving mtls.internal.example (mtls.internal.example)... 192.0.2.50
    Connecting to mtls.internal.example (mtls.internal.example)|192.0.2.50|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 84231 [text/csv]
    Remote file exists.

    A certificate or trust failure at this stage usually points to the wrong CA bundle, the wrong certificate/key pair, or a host name that does not match the server certificate.

  6. Run the actual transfer with the same certificate, key, and CA bundle after the handshake check succeeds.
    $ wget \
      --certificate="$HOME/.local/share/wget/mtls/client-cert.pem" \
      --private-key="$HOME/.local/share/wget/mtls/client-key.pem" \
      --ca-certificate="$HOME/.local/share/wget/mtls/ca-chain.pem" \
      https://mtls.internal.example/reports/nightly.csv
    --2026-03-27 06:55:29--  https://mtls.internal.example/reports/nightly.csv
    Resolving mtls.internal.example (mtls.internal.example)... 192.0.2.50
    Connecting to mtls.internal.example (mtls.internal.example)|192.0.2.50|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 84231 [text/csv]
    Saving to: 'nightly.csv'
    
         0K ...................................................... 100% 11.8M=0.007s
    
    2026-03-27 06:55:29 (11.8 MB/s) - 'nightly.csv' saved [84231/84231]

    Keep the first authenticated transfer small when possible so certificate problems are resolved before a large artifact is requested.

  7. Persist the identity in the user startup file only when the same account repeatedly talks to the same protected service.
    ~/.wgetrc
    certificate = /home/user/.local/share/wget/mtls/client-cert.pem
    private_key = /home/user/.local/share/wget/mtls/client-key.pem
    ca_certificate = /home/user/.local/share/wget/mtls/ca-chain.pem

    Only write account-specific absolute paths into ~/.wgetrc; do not copy shared examples with someone else's home directory.

  8. Verify the downloaded payload before handing it to a downstream job.
    $ head -n 3 nightly.csv
    date,total,status
    2026-03-27,42,complete
    $ ls -lh nightly.csv
    -rw-r--r--  1 user user 82K Mar 27 06:55 nightly.csv

    Successful mutual TLS is not enough on its own; the saved file still needs the expected name, size, and content marker.