LANG
Sets the default locale for all locale categories that are not explicitly set by a more specific LC_* variable. Affects language, character encoding, date/time formatting, and number formatting. The value typically includes language, territory, and encoding.
Last updated:
LANG sets the default locale — language, region, and crucially the character encoding — for any locale category not overridden by a more specific LC_* variable. The encoding suffix (`.UTF-8`) is the part that breaks real programs: when LANG is unset or `C`/`POSIX`, the system assumes ASCII, and tools like Python (pre-3.7), Perl, Click, and psql throw UnicodeDecodeError or mangle non-ASCII output. This is the classic 'works on my laptop, fails over SSH/in Docker' bug, because minimal server and container images ship with no UTF-8 locale generated.
- Provider
- General / OS
- Category
- locale
- Set by
- Set in /etc/locale.conf, shell profile, or system settings
- Example
- en_US.UTF-8
How to set LANG
bash
export LANG=en_US.UTF-8
locale # verify what is actually activeDocker (portable UTF-8, no locale-gen)
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8This page covers LANG together with LC_ALL and the LC_* family, because they only make sense as a system — most locale bugs are precedence bugs.
LANG vs LC_ALL vs LC_*: who wins?
POSIX defines a strict precedence per category: LC_ALL beats the specific LC_* variable, which beats LANG. So LANG is the default, LC_CTYPE/LC_COLLATE/LC_TIME and friends are per-category overrides, and LC_ALL is the sledgehammer that overrides everything — including your carefully chosen per-category settings. The practical division of labor: set LANG in login configuration, reach for individual LC_* when you want, say, English messages with European date formats, and reserve LC_ALL for scripts that need deterministic behavior for the duration of one command. An LC_ALL exported in .bashrc means every locale knob on the system is welded in place.
locale # shows the effective value per category and what set it
locale -a # every locale actually generated on this machine
# deterministic byte-order sorting for one command, the right way:
LC_ALL=C sort data.txtWhy scripts behave differently under another locale
The encoding suffix gets the attention, but LC_COLLATE is the silent saboteur: it changes sort order, glob expansion order, and even what ranges like [a-z] match in some tools. A pipeline that depends on sort | comm or join produces different output under en_US.UTF-8 than under C — and "works on my machine, fails in CI" is frequently exactly this, because CI runners default to C/POSIX while laptops run UTF-8 locales. There's a performance angle too: byte-comparison in the C locale lets grep and sort skip collation entirely, which is why LC_ALL=C grep on large files has historically been dramatically faster than the same command under a UTF-8 locale. Prefix locale-sensitive commands explicitly in scripts; don't inherit whatever the runner happens to have.
The SSH locale-forwarding bug
The most famous locale error message — perl: warning: Setting locale failed followed by a wall of LC_* diagnostics — is usually not the server's fault. Debian/Ubuntu ship OpenSSH with SendEnv LANG LC_* in the client config and AcceptEnv LANG LC_* on the server, so your laptop's locale rides along with the SSH session. A macOS Terminal famously sends LC_CTYPE=UTF-8 — a value that isn't a valid locale name on Linux — and every Perl-based tool on the server (including apt's maintainer scripts) starts complaining. Fix it on either side: generate the missing locale on the server (locale-gen en_US.UTF-8), or stop forwarding by commenting out SendEnv client-side (or unchecking "Set locale environment variables" in Terminal.app).
C.UTF-8: the container-era locale
For years the choice in minimal environments was bad either way: C (always present, but ASCII — Python 3.6-era UnicodeDecodeErrors, mangled box-drawing characters) or en_US.UTF-8 (UTF-8, but only if someone ran locale-gen first). C.UTF-8 resolves the dilemma — C semantics, UTF-8 encoding, no generation step. Debian has shipped it for a decade, glibc 2.35 (2022) made it universal upstream, and standardization is proposed for a future POSIX issue (Austin Group #1548). In Dockerfiles, ENV LANG=C.UTF-8 is the one-liner that prevents the whole bug class. Python deserves its own footnote: PEP 538 and PEP 540 (Python 3.7) made the interpreter coerce a plain-C locale to UTF-8 on its own, and PEP 686 finishes the job by making UTF-8 mode the default in Python 3.15 — so modern Python is increasingly locale-proof even when the environment isn't. Other runtimes (Perl, Ruby, psql, anything written in C) still need the variable set correctly. See the Docker environment variables guide for where ENV lines like this belong, and the Python environment variables guide for PYTHONIOENCODING and friends.
A debugging checklist
- Run
localefirst. Values shown in quotes are inherited defaults; unquoted ones are explicitly set. If LC_ALL has a value, nothing else you change will matter. - Check the locale exists: a variable set to a locale that
locale -adoesn't list fails silently back to C. - Mojibake (UTF-8 shown as
ö) means the data is fine but LC_CTYPE says ASCII/Latin-1; question-mark replacement means the terminal or tool truly dropped the bytes. - In containers and CI, set
LANG=C.UTF-8explicitly and treat any reliance on the host's locale as a bug.
References
Frequently Asked Questions
How do I fix UnicodeDecodeError / mangled UTF-8 over SSH or in Docker?
The environment is missing a UTF-8 locale. Set LANG (and often LC_ALL) to a UTF-8 value. In containers prefer C.UTF-8, which is always available; on a full system use en_US.UTF-8 after generating it with locale-gen. Run `locale` to confirm the encoding actually took effect.
Stay up to date
Get notified about new guides, tools, and cheatsheets.