curl Config Files: ~/.curlrc, --config, and the Flags Worth Persisting
Every curl invocation you type loads a config file first, unless you tell it not to. That is not a metaphor for "sometimes it reads a dotfile" — the default file is read on every run, including runs where you passed --config yourself. Most people's relationship with this file is: they have never written one, and one is changing their output. Verified here against curl 8.5.0 with a socket-capture server, so the wire traces below are observations, not folklore.
The file format in four rules
One option per physical line. # starts a comment when it is the first non-blank character. Long option names may drop their leading -- and use : or = as the separator; short options keep the dash and take whitespace or =. Values containing whitespace, or starting with : or =, must be wrapped in double quotes, and inside those quotes \\, \", \t, \n, \r, \v are the available escapes. A backslash before any other letter is silently dropped — a real footgun, since "foo\nbar" is a newline but "foo\ + backtick + bar" is just "foobar".
A URL cannot appear bare. Config lines are options, and the option you want is url:
# ~/.curlrc — personal defaults
user-agent = "curl/8.5 (workstation; +https://example.com/ops)"
location
max-time = 30
connect-timeout = 5
retry = 2
retry-delay = 1
show-error
location and show-error are boolean flags: no value, no =, no quotes. The moment you write location = true, curl parses true as a filename argument and produces a confusion you will spend twenty minutes on.
Which file, in which order
-K/--config is explicit and repeatable — later files are read after earlier ones, so the last one to set an option wins, exactly like a command line. The default file is a different mechanism, searched in this order:
$CURL_HOME/.curlrc$XDG_CONFIG_HOME/curlrc(added in 7.73.0)$HOME/.curlrc- On Windows:
%USERPROFILE%\.curlrc,%APPDATA%\.curlrc,%USERPROFILE%\Application Data\.curlrc, and finally the directory the curl binary lives in.
CURL_HOME beats XDG_CONFIG_HOME, which beats HOME. Windows checks both .curlrc and _curlrc per location, preferring the former; the underscore variant is a legacy of an era when a dotfile was awkward to ship.
Precedence, stated once: command line beats config file. The default file is applied first as a base, then --config files, then the literal arguments — so any flag you type overrides the same flag in any file. Verified live: a config with request = "POST" under a command line containing -X PUT put PUT on the wire.
The exception people get wrong is -q/--disable. It suppresses the default file only. If you pass --config ./shared.curlrc, then -q does nothing useful — your explicit file is still read. Verified: -q --config cfg1 still sent the config's POST and User-Agent. There is no flag that means "ignore all config, I want stock curl". The workaround is an env-var trick that is not a workaround: point CURL_HOME at an empty directory for that one invocation.
CURL_HOME=/nonexistent curl --trace-ascii - https://api.internal/health
Which flags belong there, and which absolutely don't
Belong, because they are preferences that never change per-call: user-agent, location, show-error, connect-timeout, max-time, retry, retry-max-time, speed-limit, disable-progress-meter, fail-with-body, proto/proto-redir if you want a hard scheme policy.
Do not put in a shared or committed file: insecure, any *-password, any cookie/cookie-jar pointing somewhere world-readable, output (the second you set it, every curl writes to one path and you lose the parallel-download semantics of -O/-o), proxy unless the proxy is genuinely machine-wide and non-secret, and header unless the header is non-sensitive and universal — a global Authorization header leaks to every host you ever curl, including the ones you were redirected to.
On the -X question that circulates in ops channels: yes, you can set the method from a config file. Two spellings work, and one of them complains:
request = "POST" # long form: quiet, use this
-X "PUT" # short form: works, but warns
-X = "PUT" # works, warns twice, do not ship
With -X = "PUT" curl 8.5 emits warning: '-X' uses unquoted whitespace / This may cause side-effects. Consider using double quotes? — the warning is about the space before =, and it is printed on stderr, which will corrupt any script that treats stderr as clean. Use request = "POST". The deeper point stands, though: pinning a method in a file that gets read on every invocation means every GET you attempt silently becomes something else. Keep methods on the command line, keep defaults in the file.
Unknown option lines are a warning, not an error, and exit stays 0. cfg4:1: warning: 'not-a-real-flag' is unknown and then the request runs. That is good for forward-compatibility and terrible for CI hygiene: a typo'd flag in a shared file means your team's "hardening" config quietly isn't applied. Add a smoke step that greps stderr for is unknown.
A team-shared file for CI
Check this into the repo, pass it with --config, and your pipeline stops re-deciding the same five things:
# .curlci — curl --config .curlci <url>
silent
show-error
fail-with-body
location
max-redirs = 5
connect-timeout = 5
max-time = 30
retry = 3
retry-delay = 2
speed-limit = 1024
speed-time = 20
proto = https
proto-redirs = https
write-out = "%{http_code} %{time_total}s %{url_effective}\n"
Two honest notes. retry retries transient connection failures, not 5xx — pairing retry with fail-with-body does not give you "retry the 503", and the loop that works in a shell is the loop you write. speed-limit/speed-time is the guard against a 200 that trickles: below 1 KB/s for 20 seconds and curl aborts with a non-zero exit, which is what makes the step fail loudly instead of hanging until the runner timeout.
--config - reads from stdin, which is how you template one file across matrix jobs:
sed "s|__TIMEOUT__|${JOB_TIMEOUT:-30}|" .curlci | curl --config - "$URL"
FAQ
Does -q disable everything? No. -q/--disable skips the default config file lookup (CURL_HOME, XDG_CONFIG_HOME, HOME). Explicit --config files are still parsed.
Why did my URL start downloading into a file named true? You wrote a boolean flag with a value (location = true). Booleans in a config file take no value.
Can I set the request method in ~/.curlrc? You can (request = "POST", verified on the wire), and you shouldn't. It applies to every call, and the short -X form triggers an unquoted-whitespace warning on stderr.
Is a bad line in the config file fatal? No — curl warns and continues, exit 0. Fail CI by asserting on the warning text instead.
Where do I put per-machine settings if the shared file is in the repo? In your own ~/.curlrc or CURL_HOME. The default file loads as the base; the explicit --config file and the command line both override it.
Related: flag fundamentals in the cheatsheet, and the redirect/auth traps a global config can amplify in patterns and gotchas.