curl -u username:password https://example.com is the whole feature. curl base64-encodes the pair and sends it as an Authorization: Basic header. The interesting part is not the flag — it is that typing the password on the command line puts it in your shell history and in the process list, where other users on the machine can read it.
Basic authentication is simple to the point of being crude: a username and password, base64-encoded, sent on every request. Base64 is encoding, not encryption, so the entire security of the scheme rests on TLS. Over HTTPS it is fine for machine-to-machine access. Over plain HTTP it is a credential broadcast.
Table of contents
- The forms that work
- Keeping the credential out of history and process lists
- The other authentication schemes
- Debugging when it fails
- Basic auth in a script
- When not to use basic auth
- How this fits the rest of the stack
- FAQ
The forms that work
# the standard flag
curl -u alice:secret https://api.example.com/v1/status
curl --user alice:secret https://api.example.com/v1/status
# omit the password and curl prompts, keeping it out of history
curl -u alice https://api.example.com/v1/status
# build the header yourself
curl -H "Authorization: Basic YWxpY2U6c2VjcmV0" https://api.example.com/v1/status
The last two are equivalent. -u does nothing more than base64-encode user:password and set that header, which is worth knowing when you are debugging against something that logs raw headers.
Generating the token by hand, if you need it for another tool:
printf 'alice:secret' | base64
# on Linux, avoid line wrapping on long credentials
printf 'alice:secret' | base64 -w 0
Use printf rather than echo. echo appends a newline by default, which gets encoded into the token and produces a credential the server rejects with no useful explanation. That trailing newline has cost a lot of people an afternoon.
The second form — -u alice with no colon — is the one to prefer interactively. curl prompts for the password, reads it without echoing, and it never touches your shell history.
Keeping the credential out of history and process lists
The command line is visible in two places you may not have considered: your shell’s history file, and the process table, where any user on the machine can see it with ps.
Options, in increasing order of safety:
# 1. prompt, as above
curl -u alice https://api.example.com
# 2. from an environment variable -- still visible in ps on some systems
curl -u "alice:$API_PASSWORD" https://api.example.com
# 3. from a netrc file -- nothing on the command line at all
curl -n https://api.example.com
curl --netrc-file ./secrets-netrc https://api.example.com
# 4. read the config from a file
curl --config ./curl-config https://api.example.com
The netrc route is the cleanest for repeated use. Create ~/.netrc with restrictive permissions:
machine api.example.com
login alice
password secret
chmod 600 ~/.netrc
curl reads it automatically with -n, matching on hostname. Nothing appears on the command line, so nothing lands in history or the process table. The 600 permission is not optional — many tools refuse to use a world-readable netrc, and the ones that do not should.
The --config file approach is the general version: put any curl options, including user = "alice:secret", in a file and reference it. Useful when you also want to pin headers and options for a particular API.
And prefix the command with a space if your shell is configured to ignore space-prefixed commands in history. It is a small habit that covers the one-off case.
The other authentication schemes
Basic is one of several curl supports, and the flag selects which.
curl --basic -u alice:secret https://example.com
curl --digest -u alice:secret https://example.com
curl --ntlm -u alice:secret https://example.com
curl --negotiate -u alice:secret https://example.com
curl --anyauth -u alice:secret https://example.com
# through a proxy
curl --proxy-user alice:secret --proxy http://proxy.example.com:8080 https://example.com
--anyauth makes curl probe the server and pick whichever method it advertises. Convenient, and it costs an extra round trip because curl sends an unauthenticated request first to read the challenge. Specify the method when you know it.
Digest was designed so the password never crosses the wire, which mattered before TLS was universal. Today it is largely legacy — over HTTPS, basic is simpler and no less secure in transit.
For modern APIs, bearer tokens are far more common than basic auth:
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/me
# curl 7.61+ has a dedicated flag
curl --oauth2-bearer "$TOKEN" https://api.example.com/v1/me
A token can be scoped, expired, and revoked individually. A password cannot. Where an API offers both, use the token.
Debugging when it fails
# see the headers you sent and received
curl -v -u alice:secret https://api.example.com/v1/status
# response headers only
curl -I -u alice:secret https://api.example.com/v1/status
# include headers with the body
curl -i -u alice:secret https://api.example.com/v1/status
# full trace, including the exact bytes
curl --trace-ascii - -u alice:secret https://api.example.com
Reading the status code first narrows it immediately:
- 401 Unauthorized — credentials missing, wrong, or the wrong scheme. Check the
WWW-Authenticateresponse header, which names the scheme the server wants. - 403 Forbidden — authenticated successfully, but not permitted. A permissions problem, not a credentials one. These two get confused constantly and they point in different directions.
- 404 on an authenticated endpoint — some APIs return 404 rather than 403 to avoid confirming a resource exists. Worth suspecting when the path is definitely correct.
- Works with
-u, fails with a hand-built header — nearly always a trailing newline fromecho, or wrapped base64 output from a long credential.
One curl behaviour that surprises people: by default, credentials are not resent after a redirect to a different host, for good reason. --location-trusted overrides that and should be used only when you are certain about where the redirect leads.
Basic auth in a script
For anything automated, the priorities change from convenience to failing loudly.
#!/usr/bin/env bash
set -euo pipefail
: "${API_USER:?API_USER not set}"
: "${API_PASS:?API_PASS not set}"
response=$(curl --fail --silent --show-error \
--netrc-file "$NETRC_FILE" \
--max-time 30 \
--retry 3 --retry-delay 2 \
https://api.example.com/v1/status)
echo "$response" | jq -r '.state'
--failreturns a non-zero exit code on 4xx and 5xx instead of printing the error body and exiting zero. This is the flag that prevents a script from carrying on with an error page as its data.--silent --show-errorsuppresses the progress meter but keeps real errors visible.--max-timestops a hung request from stalling the script indefinitely.--retryhandles transient failures. Combine with--retry-connrefusedwhen the target may still be starting.- The
${VAR:?message}form fails immediately with a clear message when a required variable is unset.
In CI, take credentials from the platform’s secret store rather than a file in the repository, and confirm the CI system masks them in logs. A curl -v in a build log with an Authorization header in it is a leaked credential, and build logs are frequently more widely readable than the repository.
When not to use basic auth
It has a narrow band where it is the right answer, and outside that band there are better options.
- Never over plain HTTP. Base64 is trivially reversible; anyone on the path reads the password. If a service only offers HTTP, treat the credential as public.
- Not for user-facing login. No logout, no session control, no expiry, credentials sent on every request. Use sessions or tokens.
- Not where the credential cannot be rotated. Basic auth against a shared account with a password nobody can change is a liability that grows over time.
- Not when the API offers tokens. Scoped, expiring, individually revocable credentials are strictly better.
Where it is genuinely fine: machine-to-machine over HTTPS against an internal service, a health check endpoint, a registry, or a quick manual test against an API that only supports it. Simple, universally supported, and adequate for exactly those cases.
How this fits the rest of the stack
The consistent thread here is that a credential’s biggest risk is not the protocol but where the value ends up sitting — shell history, a process list, an image layer, a build log. Environment variables supplied by the platform at runtime avoid most of that: on RunxBuild they are set per service in the dashboard, injected when the process starts, and changed without a rebuild, so rotating an API password is a restart rather than a new deploy. Services on RunxBuild covers environment variables alongside deploy logs and rollback. When you are sizing a service that talks to an authenticated API plus its own database, the RunxBuild hosting calculator shows each part as a separate line.
Useful related references:
- cURL Error 28: Which Timeout Fired and Why It Matters
- curl -k: Ignoring Certificate Errors, and Why You Should Fix Them Instead
- n8n HTTP Request Node: The Auth and Error Playbook
- Response headers on RunxBuild
FAQ
How do I send basic auth with curl?
Use curl -u username:password https://example.com. curl base64-encodes the pair and sends it as an Authorization: Basic header. Omit the password — curl -u username — and curl prompts for it instead, which keeps it out of your shell history.
How do I avoid putting my password in shell history?
Use -u username without the password so curl prompts, or put the credentials in ~/.netrc with 600 permissions and use curl -n. The netrc route puts nothing on the command line, so nothing appears in history or in the process table where other users can read it.
Why does my hand-built Authorization header fail when -u works?
Almost always a trailing newline. echo 'user:pass' | base64 encodes the newline that echo appends. Use printf 'user:pass' | base64 instead, and add -w 0 on Linux so long credentials are not wrapped across lines.
What is the difference between a 401 and a 403 from curl?
401 means the credentials are missing, wrong, or in the wrong scheme — check the WWW-Authenticate response header for what the server wants. 403 means you authenticated successfully but are not permitted to do that. They point at completely different fixes.
Is curl basic auth secure?
Only over HTTPS. Base64 is encoding, not encryption, so over plain HTTP the password is readable by anyone on the network path. Over TLS it is acceptable for machine-to-machine access, but a scoped bearer token is better wherever the API offers one, since it can expire and be revoked individually.