Fiber optic patch cables connected to a blue patch panel in a data center

curl 8.23.0 removes SMB

November 5, 2026 · 11 min read · By Thomas A. Anderson

If your CI pins libcurl 8.22.0, upgrading to 8.23.0 changes three things that can break a job today: smb:// transfers stop working entirely, CURL_GLOBAL_WIN32 becomes the deprecated spelling of CURL_GLOBAL_WINSOCK, and --no-clobber writes report-1.pdf where it used to write report.pdf.1.

The project’s own release notes for 8.23.0 list the behavioral diffs in one short block, apart from the bugfix list. There are four lines in it, and each one lands on a different group of users:

  • lib: CURL_GLOBAL_WIN32 -> CURL_GLOBAL_WINSOCK renames a public flag used with curl_global_init().
  • SMB: drop support removes a protocol that had already become opt-in in an earlier release.
  • tool_cb_wrt: inject no-clobber number before extension changes the filenames the CLI produces.
  • tool_cb_wrt: try up to .9999 when no-clobbering sets the ceiling for that counter.

Everything else in the notes is bugfixes, documentation corrections and build cleanups. The bugfixes are why you would want the upgrade, and those four lines are why you should schedule it instead of letting a floating tag pull it in overnight.

Key Takeaways:

  • SMB support is listed under past removals: it became opt-in in 8.20.0 and is removed in 8.23.0, so no curl build in this release can service an smb:// transfer.
  • CURL_GLOBAL_WIN32 becomes CURL_GLOBAL_WINSOCK. The release announcement describes the old symbol as deprecated in favor of the new name.
  • The flag’s job is Winsock initialization. The man page says most of libcurl’s Windows initialization is required and is not controlled by the flag, so removing or adding it does not switch Windows support on or off.
  • --no-clobber now inserts -[num] before the extension: image-1.jpg, then image-2.jpg. Downloads without an extension keep the older shape.
  • Grep for the old flag, rebuild with warnings as errors, re-run anything that fetches over SMB, and re-test scripts that assert exact output filenames.

Four lines in the 8.23.0 changelog

The notes separate “changes” from “bugfixes”, which makes the behavior diffs easy to isolate. These are the entries that touch a build script or an embedder:

Changelog entry Kind of change Who has to act
lib: CURL_GLOBAL_WIN32 -> CURL_GLOBAL_WINSOCK Public flag rename Windows embedders that name the flag in source or in documentation
SMB: drop support Protocol removal Anyone with a job, cron entry or build that targets smb://
tool_cb_wrt: inject no-clobber number before extension CLI naming behavior Scripts that predict, glob or parse downloaded filenames
tool_cb_wrt: try up to .9999 when no-clobbering CLI naming ceiling Retry loops and parsers that assume an unbounded counter

One further entry is documentation, and it is the one to read if you maintain build notes: DEPRECATE.md: reflect SMB removal. The project keeps its removal ledger in that file, so an SMB line moving out of the “future removals” section is the signal that the code is gone rather than scheduled.

If you pin curl for stability rather than features, the fixes are the reason to move and these four lines are the reason to schedule the move. The same checklist shape works for other toolchain bumps, which is how we handled the Go runtime and toolchain changes.

SMB support is out of the tree

The deprecation document records it in two entries. The “past removals” list ends with “SMB support (removed in 8.23.0)”, and an earlier line records that SMB “became opt-in in 8.20.0”. Two releases ago it stopped being part of a default build. Now the code is out, alongside RTMP (removed in 8.20.0) and TLS-SRP (removed in 8.22.0).

A URL such as smb://fileserver/finance/2026-q3-close.xlsx has no protocol implementation behind it in this release, so the transfer cannot start and the process exits non-zero. A monthly job that pulled a spreadsheet from a share does not degrade into a smaller download; it fails outright. Whether anyone notices depends on whether the wrapper checks the exit status or just logs stdout.

# Find SMB targets in scripts, CI configs and Makefiles before you upgrade
grep -rIn --include='*.sh' --include='*.yml' --include='*.yaml' \
 --include='Makefile' 'smb://' ./ops ./ci

# Confirm the job fails loudly on 8.23.0 instead of failing quietly
curl -sS --max-time 20 smb://fileserver/finance/2026-q3-close.xlsx \
 -o /tmp/2026-q3-close.xlsx
echo "exit: $?"
# Expect a non-zero exit and no file. Nothing transfers, because the
# protocol is no longer part of curl.
# Note: point this at a staging share, not the production file server,
# and keep the timeout so an unreachable host fails the check instead of
# hanging the pipeline.

The build side needs its own pass. Because SMB was opt-in from 8.20.0, a build that deliberately kept it enabled is the one that changes shape now. Re-run your configure or CMake step against the new version and read the output rather than editing the invocation from memory; the notes do not document a replacement option, and guessing one is how a build silently loses a feature instead of erroring.

There is no drop-in curl replacement for SMB, so the work is a migration rather than a rename. Find the consumers, choose a different client, and confirm it can authenticate to the same share with the credentials the old job used. The deprecation document lists the removal and does not describe a migration path.

CURL_GLOBAL_WIN32 is renamed to CURL_GLOBAL_WINSOCK

The release announcement for 8.23.0 says the symbol formerly known as CURL_GLOBAL_WIN32 “is now deprecated in favor of the more accurately named CURL_GLOBAL_WINSOCK“, used with curl_global_init(). The changelog line is a plain arrow, which is the project’s shorthand for a rename rather than a deletion.

The curl_global_init(3) page also explains why the old name caused confusion. The flag initializes the Windows sockets library, and nothing else: “Most of libcurl’s Windows initialization is required and is not controlled by this flag.” The page adds that the option exists for applications that have already initialized Winsock and want libcurl to skip the work.

#include <curl/curl.h>

/* Windows builds still link ws2_32. The rename does not change the link
 line, the DLL name, or the calling convention. */
int fetch_report(const char *url, const char *path)
{
 CURLcode rc = curl_global_init(CURL_GLOBAL_DEFAULT);
 if(rc != CURLE_OK)
 return 1; /* the docs are explicit: nothing else is usable */

 /* curl_easy_init(), curl_easy_setopt(), curl_easy_perform() ... */

 curl_global_cleanup();
 return 0;
}

/* If the app initializes Winsock itself, the bit it ORs in is now
 CURL_GLOBAL_WINSOCK. Keep your own vendored curl.h in sync before you
 assume the old spelling resolves; the changelog is not the header.
 Note: this example omits per-handle error handling and says nothing
 about threading; check the man page before calling it from a DLL. */

Three constraints from the same page matter more than the spelling. The function must be called at least once before any other libcurl call, and the environment it sets up stays constant for the life of the process. In normal operation you pass CURL_GLOBAL_ALL and do not hand-pick bits. If you initialize from a Windows DLL, do not do it in DllMain or a static initializer, because Windows holds the loader lock there and the call can deadlock.

Flag What it initializes
CURL_GLOBAL_ALL Everything possible: every known bit except CURL_GLOBAL_ACK_EINTR
CURL_GLOBAL_DEFAULT SSL and Winsock; the page notes this currently equals the all-flags mask
CURL_GLOBAL_WINSOCK Winsock; omit it only when the application has already initialized Winsock
CURL_GLOBAL_NOTHING No extra bits

CURL_GLOBAL_ACK_EINTR is worth a mention because it looks like a behavior switch and is not. The page states it has had no point since 7.69.0, because acknowledging interrupted calls became the default. Code that still ORs it in is carrying dead configuration, not extra safety.

–no-clobber puts its counter before the extension

The option’s documentation states the new rule in one sentence: starting in 8.23.0, if the target filename uses an extension, curl creates the new name by inserting -[num] before the extension. Downloading to image.jpg with --no-clobber now produces image-1.jpg, then image-2.jpg. Before this release, dot and number were appended to the name, which produced image.jpg.1.

# report.pdf already exists in the working directory
curl -sS --no-clobber --output report.pdf https://reports.example.com/2026-q3.pdf
curl -sS --no-clobber --output report.pdf https://reports.example.com/2026-q3.pdf
ls report*

# 8.23.0 and newer:
# report.pdf report-1.pdf report-2.pdf
# Earlier releases produced the counter after the extension:
# report.pdf report.pdf.1 report.pdf.2
#
# A target with no extension still takes the older shape:
# data data.1 data.2
# Note: run this in a scratch directory. What you get depends on which
# files already exist, and a shared download folder will confuse the test.

Two properties survived the change. The ceiling is still .9999: the documentation says curl tries up to that point and then stops creating files, so a retry loop that fails a thousand times ends with no new file rather than a fifth-digit name. And --continue-at still cannot be combined with --no-clobber; the page states the incompatibility without qualification.

The documentation also reminds readers that the documented name is the negated one, so --clobber is how you force overwriting, including when --remote-header-name is set. Nothing about that interaction changed here. The option itself is not new: it was added in 7.83.0, and its original job, avoiding overwrites when used with --output, --remote-name, --remote-name-all or --remote-header-name, is intact.

Checks to run before the upgrade lands

Four checks cover the realistic failure modes, ordered from cheapest to most expensive. The last one needs a reachable server, so it belongs in staging rather than a laptop.

  • Grep your sources for the old flag. Every hit is a rename to schedule, and every hit outside your code is a document to correct.
  • Rebuild with warnings as errors. This is the only check that proves your headers and your source agree on the new spelling.
  • Re-run every SMB-dependent job. Confirm the failure is loud, then confirm the replacement client authenticates to the same share.
  • Re-run the download scripts. Anything that globs *.pdf.1, sorts by suffix, or builds a filename by appending an index now sees different names.
# Rename backlog: uses of the old spelling in your own code
grep -rn --include='*.c' --include='*.h' 'CURL_GLOBAL_WIN32' ./src

# Prove the new name resolves with this header set, and fail on warnings
cc -Werror -Wall -Wextra -I/usr/local/include report_fetch.c \
 -lcurl -o report_fetch
echo "build exit: $?"
# Expect 0 for a clean build against 8.23.0 headers.
# Note: -Wextra applied to third-party headers can produce warnings you
# cannot fix; scope the flags to your own translation units if so.

The filename check deserves an executable test rather than a code review. Run the fetch three times in a clean directory, then assert on the exact names you expect. A three-line shell assertion answers the question in seconds and keeps answering it after every future upgrade, which a manual check does not.

Trade-offs and the removals behind this one

Removing SMB simplifies the code base and takes away a protocol some teams used for scripted file moves inside a Windows-heavy network. The second half of that is a real cost, and the release does not soften it. The replacement is a different client with different flags, different authentication handling and different exit codes, so the change amounts to a job rewrite dressed up as a URL change.

The flag rename costs nothing at runtime and creates paperwork. Internal runbooks, build notes and wiki pages that name CURL_GLOBAL_WIN32 are now stale, and because the old symbol is described as deprecated rather than deleted, nothing will force anyone to notice for a while.

The --no-clobber change fixes a real annoyance, since a directory full of .pdf.1 files sorts badly and reads worse. It also breaks any parser that learned the old pattern, and that cost lands on whoever wrote the parser, not on the people who benefit from the new names.

The larger scheduling issue is the rest of the ledger. The same deprecation document lists NTLM removal with a September 2026 date, local crypto implementations for October 2026, HTTP/2 Server Push for March 2027, and Common Name support in TLS certificates for the release after 8.23.0. NTLM still appears in the past-removals list as opt-in rather than removed, so check what your build actually enables instead of assuming the date did the work. For a team shipping libcurl inside a product, those four items are the roadmap question, and SMB is already gone.

What the release counters say about the project

The header block of the 8.23.0 release notes opens with a tally rather than prose. It prints “Public curl releases: 276”, “Command line options: 278”, “curl_easy_setopt() options: 312”, “Public fns in libcurl: 100”, “Authors: 1547” and “Contributors: 3844”. Those counters sit at the top of the project’s own document, and they give you the scale to weigh a four-line change list against.

The same header is where the release identifies itself as curl and libcurl 8.23.0, before any changelog entry. That ordering is useful when you are diffing two versions: the counters and the version line tell you what document you are reading, and the changes block tells you what moved since the last one.

The release notes remain the project’s own account of its changes. They say what moved, not how many deployments depended on it, so treat the impact statements above as consequences of documented behavior rather than surveyed outcomes.

More in-depth coverage from this blog on closely related topics:

Sources and References

Sources cited while researching and writing this article:

Thomas A. Anderson

Mass-produced in late 2022, upgraded frequently. Has opinions about Kubernetes that he formed in roughly 0.3 seconds. Occasionally flops, but don't we all? The One with AI can dodge the bullets easily; it's like one ring to rule them all... sort of...