Troubleshooting

Cntlm or Px: which local proxy should get your dev tools through an NTLM or Kerberos corporate proxy?

Px for Windows single sign-on with no stored password, Cntlm for a 0.3 MB C daemon. Which builds really have Kerberos, and how to wire tools to 3128.

· 15 min read · How we verify this

Key points

  • Use Px on a domain-joined Windows machine: SSPI authenticates as the logged-in user with NTLM or Kerberos and nothing is stored. Use Cntlm for a small C daemon on Linux, with the PassNTLMv2 hash from cntlm -H.
  • "Cntlm has no Kerberos" is out of date for versat/cntlm 0.94.0 (19 August 2025), but still true of the package most people install: Debian 13, Ubuntu 24.04 and Homebrew ship 0.92.3, and Ubuntu 26.04's 0.94.0+dfsg-1 was built without GSSAPI.
  • cntlm -h lists a gss auth type only when the binary was built with Kerberos. No gss in the help means no Kerberos, whatever the version says.
  • A cntlm -H hash keeps the password from being read, not from being used: anyone who copies PassNTLMv2 can authenticate to the proxy as you.
  • Both listen on 127.0.0.1:3128 by default. Point http_proxy and https_proxy at it, keep no_proxy to the portable subset, and put CIDR ranges and wildcards in the local proxy's own bypass list.

Use Px on a domain-joined Windows machine: it authenticates as the logged-in Windows user through SSPI, with NTLM or Kerberos, so no password or hash is stored anywhere. Use Cntlm when you want a small C daemon with no runtime, typically on a Linux host where you have to supply a credential anyway, and store the NTLMv2 hash that cntlm -H prints rather than the password. The claim that "Cntlm has no Kerberos" is out of date for the maintained versat/cntlm fork, whose 0.94.0 release (19 August 2025) added Kerberos, Windows SSPI (NTLM only) and PAC files, but it is still true of most Cntlm packages people actually install. Both tools listen on 127.0.0.1:3128 by default, so the tools behind them are wired identically.

Both exist for one job: authenticating to the proxy your own employer runs, on behalf of tools that cannot speak NTLM or Negotiate themselves. They log in as you, so the proxy's policy, filtering and logging apply exactly as they do to your browser. Nothing on this page is a way around that proxy.

Your situationUseWhy
Domain-joined Windows laptop, no stored secret wantedPxSSPI uses the logged-in user for NTLM and Kerberos. Cntlm's SSPI does NTLM only
Proxy accepts only Negotiate (Kerberos), you are on WindowsPxCntlm has no Kerberos path through SSPI
Linux or macOS workstation that already runs kinitEither; Px if tickets expire on youCntlm uses your ticket cache but never renews it. Px 0.11.0 and later renews it with --kerberos
Smallest footprint, nothing but libcCntlmThe upstream 0.94.0 .deb is 0.30 MB; the Px 0.11.0 Linux x86_64 binary archive is 26.15 MB
"There is no Python on this machine"EitherPx ships standalone binaries. Python 3.10 or later is needed only for pip install px-proxy
Linux CI runner with a service accountCntlm with PassNTLMv2, or Px with PX_PASSWORDBoth hold a password equivalent. Ask the proxy team for a non-NTLM route for CI first
Nothing password-equivalent may be storedPx with SSPI, or a Kerberos ticketA cntlm -H hash authenticates without the password
The proxy is published only as a PAC URLPx--pac takes a URL or a path, and with no proxy set Px reads Windows Internet Options. Cntlm's Pac takes a file path
Intranet sites that demand NTLM themselvesCntlmIn NoProxy direct mode Cntlm also performs the site's own NTLM authentication
Only distribution packages are allowedCheck the build firstSee the package table below: most ship Cntlm without Kerberos

Does Cntlm support Kerberos?#

Yes, from 0.94.0, if the binary was built with GSSAPI and a ticket is already in the cache. Each of those three conditions fails often enough in practice that the opposite claim keeps circulating.

The version. Cntlm's last release from its original author is 0.92.3. The maintained fork is versat/cntlm, and its 0.94.0 release notes say it "Added support for SSPI (on Windows, NTLM authentication only), Kerberos authentication, IPv6, proxy PAC files". Distributions have been slow to follow:

Where Cntlm comes fromVersiongss auth typePAC (-x)
versat/cntlm 0.94.0 .deb (depends on libgssapi-krb5-2)0.94.0YesYes
Debian 12 bookworm0.92.3-1.2NoNo
Debian 13 trixie0.92.3-1.3NoNo
Debian forky and sid0.94.0+dfsg-2Yes, links libgssapi_krb5.so.2Yes
Ubuntu 22.04 jammy0.92.3-1ubuntu2NoNo
Ubuntu 24.04 noble0.92.3-1.3build1NoNo
Ubuntu 26.04 resolute0.94.0+dfsg-1NoYes
Homebrew0.92.3NoNo

The Ubuntu 26.04 row is the trap. It has the new version, so it looks like the answer to "upgrade to get Kerberos", but Debian added "GSS/Kerberos support" to its package only in 0.94.0+dfsg-2 (8 June 2026). On 26.04, cntlm -h lists -a ntlm | nt | lm and the binary does not link GSSAPI.

The build. Kerberos is compiled in only when ./configure detects GSSAPI. Its gss test compiles config/gss.c against gssapi/gssapi.h (the GSS framework on macOS) and checks that libgssapi_krb5.so can be loaded; the Makefile adds -lgssapi_krb5 only if it passed. A source build on a host without the Kerberos development headers prints Checking gss... no and carries on to produce a binary with no Kerberos and no error.

The help text tells you which binary you have, because main.c prints the gss option only inside #if config_gss == 1:

bash
cntlm -h 2>&1 | grep -A2 -- '-a  '
#   -a  ntlm | nt | lm | gss
#       Authentication type - combined NTLM, just LM, just NT, or GSS. Default NTLM.
#       GSS activates kerberos auth: you need a cached credential.

That is Debian sid's 0.94.0. Debian 13's 0.92.3 and Ubuntu 26.04's 0.94.0 both print the line without gss. ldd /usr/sbin/cntlm | grep gssapi gives the same answer from the linker's side.

The ticket. Cntlm never obtains a Kerberos ticket. The manual says "You need a cached credential (see kinit doc): per default the cache file is in /tmp/krb5cc_<uid>, you can override this using the environment variable KRB5CCNAME." With Auth GSS it sends a token on each connection, and "If GSS credentials are not available will fallback to the NTLM discovery procedure". Without an Auth line it uses Kerberos only when a ticket is cached and the proxy asks for Negotiate, and NTLM otherwise. A Cntlm whose ticket has expired, or which cannot read the cache, quietly becomes an NTLM proxy, which looks like "no Kerberos" from the outside.

On Windows the question does not arise in Cntlm's favour: its single sign-on path is SSPI, and the configuration template says plainly "Only NTLM is supported for now."

How Px authenticates#

Px hands each upstream connection to libcurl and, in its README's words, "supports all the authentication mechanisms supported by libcurl". --auth defaults to ANY, which lets libcurl pick from what the proxy offers in its 407; it can be forced to NEGOTIATE, NTLM, DIGEST or BASIC, or narrowed with prefixes (NONTLM is ANY minus NTLM, ONLYNTLM allows only NTLM). How the proxy's list of schemes turns into a choice is covered in proxy authentication.

On Windows Px "automatically authenticates using the currently logged in Windows user account" through SSPI, for NTLM and Kerberos alike. Nothing is stored, and a password change needs no action because the logon session carries the new one. If you name no upstream proxy, Px "will check Internet Options or environment variables for any proxy definitions". That makes the minimal Windows setup px with no arguments, or winget install genotrance.px followed by pxw --install to start it at logon.

Elsewhere Px needs --username=DOMAIN\user and reads the password from the system keyring: Credential Manager on Windows, Keychain on macOS, Gnome Keyring or KWallet on Linux. PX_PASSWORD is the fallback "when keyring is not available", and PX_KEYRING_PLAINTEXT=1 stores it unencrypted on disk, which the documentation marks "not recommended".

Kerberos on Linux and macOS needs a ticket in the cache, as with Cntlm. The difference is what happens when it expires. Without --kerberos, the documentation is explicit: "If the ticket expires, Px will fail to authenticate until a new ticket is obtained and Px is restarted." Px 0.11.0 (3 April 2026) added --kerberos, which runs kinit with the configured user and the keyring password, renews the ticket before it expires, retries after a laptop sleep, and needs a libcurl with GSS-API.

Versions. 0.11.0 is the newest full release on GitHub. 0.12.0 (21 July 2026) is marked pre-release there, yet pip install px-proxy installed 0.12.0 on 12 October 2026, so the version you get depends on the channel. 0.12.0 adds PAC encoding detection for files served as cp1252 or cp1251 without a charset, a real problem with PAC files on IIS. Python 3.10 has been the minimum since 0.10.3 dropped 3.8 and 3.9. Standalone binaries cover Windows amd64, Linux glibc and musl on x86_64 and aarch64, and macOS arm64 only, so an Intel Mac installs through pip.

Where the credential lives#

The decision that matters to a security reviewer is not the tool but what sits on the machine afterwards.

MethodToolWhat is storedAfter a password change
SSPI single sign-onPx on Windows (NTLM and Kerberos); Cntlm on Windows (NTLM only)NothingNothing to do
Kerberos ticket from your own kinitCntlm 0.94.0 with GSS; Px without --kerberosA time-limited ticket in the cacheNext kinit uses the new password
--kerberos ticket managementPx 0.11.0 and later, Linux and macOSPassword in the keyring, ticket in a per-process cacheRe-run px --password
System keyringPx with --usernamePassword, protected by the OS keyringRe-run px --password
PX_PASSWORDPx, including the mini Docker imagePassword in the environmentUpdate the variable
PassNTLMv2 hashCntlm, from cntlm -HHash bound to one user and domainRegenerate with -H
PasswordCntlmPlaintext in cntlm.conf, mode 0600Edit the file
-I promptCntlmNothing; typed at every startType the new one

Px's own documentation warns that a password in the environment "is visible in docker inspect and /proc/<pid>/environ", which is the same leak path as credentials embedded in a proxy URL.

cntlm -H prints the hashes that replace the password in the configuration file. From Debian sid's 0.94.0, with the values replaced:

text
$ cntlm -H -u alice -d CORP -c /dev/null
Password:
PassLM          <32 hex digits>
PassNT          <32 hex digits>
PassNTLMv2      <32 hex digits>    # Only for user 'alice', domain 'CORP'

Keep only the PassNTLMv2 line, set Auth NTLMv2, and delete Password. The manual calls NTLMv2 "the most and possibly the only secure authentication of the NTLM family". If the proxy rejects it, cntlm -M <testurl> tries the known presets strongest first and prints the setting that worked.

Wiring dev tools to 127.0.0.1:3128#

A request can leave for its destination directly at two points, and only one hop carries credentials:

  1. The tool checks its own no_proxy. A match goes direct and never reaches the local proxy.
  2. Everything else goes to Cntlm or Px on 127.0.0.1:3128, with no credentials at all on that hop.
  3. The local proxy checks its own bypass list (NoProxy in Cntlm, --noproxy in Px). A match goes direct from there.
  4. Everything else goes to the corporate proxy, and the local proxy answers its 407 with NTLM or Negotiate.

A Cntlm configuration for two parent proxies:

text
# /etc/cntlm.conf (mode 0600)
Username    alice
Domain      CORP
PassNTLMv2  <from cntlm -H>
Auth        NTLMv2
Proxy       proxy1.corp.example:8080
Proxy       proxy2.corp.example:8080
NoProxy     localhost, 127.0.0.*, 10.*, *.corp.example
Listen      127.0.0.1:3128

Cntlm tries the Proxy lines "one after another until one works". The Px equivalent, saved to px.ini:

bash
px --proxy=proxy1.corp.example:8080,proxy2.corp.example:8080 \
   --noproxy=10.0.0.0/8,corp.example --save

Then point the tools at it:

bash
export http_proxy=http://127.0.0.1:3128
export https_proxy=http://127.0.0.1:3128
export HTTPS_PROXY=$https_proxy
export no_proxy=localhost,127.0.0.1,::1,corp.example
export NO_PROXY=$no_proxy

https_proxy uses an http:// URL because that is the scheme of the hop to the local proxy, not of the destination. curl reads http_proxy in lower case only, the httpoxy defence explained in corporate proxies and developer tooling, so do not rely on HTTP_PROXY alone.

Keep no_proxy portable and move everything else into the local proxy. The no_proxy environment variable has no specification, and its portable subset is narrow: comma separated, bare hostnames and literal IPs, no leading dots, no wildcards, no CIDR, both cases set. That page's advice for ranges is to "fix it at a different layer", and the local proxy is that layer. Px's --noproxy accepts IPs, wildcards, ranges, CIDR and domains; Cntlm's NoProxy accepts * and ? wildcards. Each is one parser that every tool shares, so a range written there behaves the same for Go, Python, Java and curl. The cost is that internal traffic outside the portable list passes through the local process before going direct. Loopback addresses still belong in no_proxy itself, and you can check what each runtime will do with a value in the no_proxy tester.

What gets simpler. The hop to 127.0.0.1 asks for no authentication, so no proxy URL carries user:password, nothing leaks into shell history or CI logs, and the JVM's default jdk.http.auth.tunneling.disabledSchemes=Basic never comes into play. The JVM still ignores https_proxy, so it needs -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=3128 and the matching http. properties, and npm needs proxy and https-proxy in .npmrc.

What does not change: certificates. Neither tool terminates TLS. An https:// request reaches the corporate proxy as a CONNECT tunnel, so if that proxy inspects TLS, every tool still needs the corporate root CA, as described in TLS interception and corporate root CAs. A local auth proxy fixes 407, never unable to get local issuer certificate.

Containers and WSL2. Inside a container, 127.0.0.1 is the container. The Docker daemon runs on the host and can use 127.0.0.1:3128 for pulls, but builds and running containers need the host's address: Px's documentation uses http://host.docker.internal:3128 on Docker for Windows with Px started with --hostonly, which accepts connections from every address assigned to the machine. For WSL2 it derives the Windows host address from the nameserver line in /etc/resolv.conf. With Cntlm, add a Listen line on the bridge address and restrict it with Allow and Deny.

Never run either as a shared gateway. Px's documentation describes --gateway as "every client will use your login", and Cntlm's Gateway yes makes it "accessible to other machines on the network". Either turns your identity into an open proxy on the office network, with every request logged under your name. Cntlm's optional SOCKS5 listener carries the same warning: it "accepts all requests, unless you use SOCKS5User".

Failure modes#

Auth GSS is set, but the proxy still sees NTLM. Two causes. The binary has no Kerberos (cntlm -h shows no gss), or Cntlm cannot read your ticket. The Debian packages run Cntlm as a dedicated cntlm user: User=cntlm in the 0.94.0+dfsg-2 systemd unit, RUNAS=cntlm in /etc/default/cntlm on Debian 13. Your ticket in /tmp/krb5cc_<your uid> is not its cache, so it falls back to NTLM. Point KRB5CCNAME in the unit at a cache that user can read, or run Cntlm as yourself.

Px on Linux works all morning, then every request gets a 407. The Kerberos ticket expired and Px was running without --kerberos, so it will not recover until a new ticket exists and Px is restarted. Use --kerberos on 0.11.0 or later.

Px as a Windows service authenticates as nobody. Its installation guide notes that a service running as LocalSystem "does not have access to SSPI for NTLM or Kerberos authentication". Run the service as the user, or use pxw --install, which the same guide recommends for running Px at startup with the current user's SSPI credentials.

Address already in use on 3128. Both tools default to it. Cntlm "fails to start if it cannot bind all of the proxy service ports". Move one with Listen or --port and change http_proxy to match.

Px on a headless Linux box fails with Prompt dismissed. gnome-keyring-daemon 48 and later needs ~/.local/share/keyrings owned by the current user with mode 700, and fails silently otherwise. Fix the permissions, or use PX_PASSWORD and accept the trade-off in the credential table.

Cntlm returns 407 for everything after a password change. The stored hash was computed from the old password. Run cntlm -H again and replace PassNTLMv2. Px through SSPI is unaffected.

A PAC-defined proxy changed and Cntlm did not notice. Cntlm's Pac option takes a file path, so a PAC downloaded once stays as it was. Px fetches --pac by URL. To see which proxy a PAC returns for a host, use the PAC file tester, and see PAC files and WPAD for how browsers find the file in the first place.

Where this answer stops applying#

The Px statements come from its README, usage and configuration documentation, release notes for 0.10.3 to 0.12.0, and px --help from 0.12.0. The Cntlm statements come from the versat/cntlm manual, configuration template, source and 0.94.0 release, plus the Debian 13, Debian sid and Ubuntu 26.04 packages installed in containers on 12 October 2026. Neither tool was run against a live NTLM or Kerberos proxy for this page, so whether a particular proxy accepts NTLMv2, or expects Negotiate first, is for cntlm -M or px --test to answer. Package versions move: re-check the table above before relying on a distribution build. If your proxy team can offer Basic authentication over a TLS-protected proxy hop, or source-address rules for build machines, you may not need either tool.

Frequently asked questions#

Does Cntlm support Kerberos?#

Yes, from version 0.94.0 of the maintained versat/cntlm fork, with Auth GSS and a ticket from kinit. Many packages still lack it: Debian 13, Ubuntu 24.04 and Homebrew ship 0.92.3, and Ubuntu 26.04's 0.94.0 was built without GSSAPI. If cntlm -h does not list gss, the binary has no Kerberos.

What port does Cntlm listen on by default?#

Cntlm listens on 127.0.0.1:3128 by default, set by the Listen line in cntlm.conf. Px uses the same default, so running both on one machine needs one of them moved.

Does Px need Python?#

Only if you install it with pip, which requires Python 3.10 or later. Standalone binaries for Windows, Linux and Apple Silicon Macs are on the GitHub releases page, and Windows users can install with Winget or Scoop.

Is it safe to store the cntlm -H hash instead of the password?#

It is safer than plaintext but not safe to share. The hash is enough to authenticate to the proxy as you, so treat cntlm.conf like a password file with mode 0600. Keep only PassNTLMv2 and regenerate it after every password change.

Is Cntlm still maintained?#

The original project stopped at 0.92.3, but the versat/cntlm fork is maintained and released 0.94.0 on 19 August 2025 with Kerberos, SSPI, IPv6 and PAC support. Many distributions still package 0.92.3.

Can Cntlm or Px use a PAC file?#

Both can. Px takes a URL or local path with --pac, and on Windows reads the system proxy settings when none is given. Cntlm 0.94.0 takes a local file path with Pac or -x; 0.92.3 has no PAC support.

Should I use Px or Cntlm on Linux?#

Cntlm, if you need NTLM with a small daemon and can store an NTLMv2 hash. Px, if you need Kerberos with automatic ticket renewal (--kerberos, 0.11.0 and later) or CIDR ranges in the bypass list. Check that a distribution's Cntlm package was built with GSSAPI before relying on its Kerberos support.

Primary sources#

Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.

  1. versat/cntlm repository and README
  2. versat/cntlm releases (0.94.0)
  3. Cntlm manual page, doc/cntlm.1
  4. Cntlm configuration template, doc/cntlm.conf
  5. Cntlm source: configure, config/gss.c and main.c (GSS build detection and help text)
  6. Debian cntlm package versions and 0.94.0+dfsg-2 changelog
  7. Ubuntu cntlm package publishing history
  8. genotrance/px repository and README
  9. Px releases and changelog
  10. Px usage and configuration reference

Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.

More in troubleshooting proxies#