diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index d159fe58d3eef..1a851ad04a1f0 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -212,6 +212,8 @@ codegen_ssa_rlib_only_rmeta_found = could not find rlib for: `{$crate_name}`, fo
 
 codegen_ssa_select_cpp_build_tool_workload = in the Visual Studio installer, ensure the "C++ build tools" workload is selected
 
+codegen_ssa_self_contained_linker_missing = the self-contained linker was requested, but it wasn't found in the target's sysroot, or in rustc's sysroot
+
 codegen_ssa_shuffle_indices_evaluation = could not evaluate shuffle_indices at compile time
 
 codegen_ssa_specify_libraries_to_link = use the `-l` flag to specify native libraries to link
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 6939674ce9dd7..9d19ee77d3dbf 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -3116,13 +3116,21 @@ fn add_lld_args(
 
     let self_contained_linker = self_contained_cli || self_contained_target;
     if self_contained_linker && !sess.opts.cg.link_self_contained.is_linker_disabled() {
+        let mut linker_path_exists = false;
         for path in sess.get_tools_search_paths(false) {
+            let linker_path = path.join("gcc-ld");
+            linker_path_exists |= linker_path.exists();
             cmd.arg({
                 let mut arg = OsString::from("-B");
-                arg.push(path.join("gcc-ld"));
+                arg.push(linker_path);
                 arg
             });
         }
+        if !linker_path_exists {
+            // As a sanity check, we emit an error if none of these paths exist: we want
+            // self-contained linking and have no linker.
+            sess.dcx().emit_fatal(errors::SelfContainedLinkerMissing);
+        }
     }
 
     // 2. Implement the "linker flavor" part of this feature by asking `cc` to use some kind of
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index ed6a0c2441061..3641e7842cfbb 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -413,6 +413,10 @@ pub struct UnableToExeLinker {
 #[diag(codegen_ssa_msvc_missing_linker)]
 pub struct MsvcMissingLinker;
 
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_self_contained_linker_missing)]
+pub struct SelfContainedLinkerMissing;
+
 #[derive(Diagnostic)]
 #[diag(codegen_ssa_check_installed_visual_studio)]
 pub struct CheckInstalledVisualStudio;
diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs
index f4e2436efb9fb..9cb8cd836e6bf 100644
--- a/compiler/rustc_session/src/filesearch.rs
+++ b/compiler/rustc_session/src/filesearch.rs
@@ -51,6 +51,14 @@ pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf {
     PathBuf::from_iter([sysroot, Path::new(&rustlib_path), Path::new("lib")])
 }
 
+/// Returns a path to the target's `bin` folder within its `rustlib` path in the sysroot. This is
+/// where binaries are usually installed, e.g. the self-contained linkers, lld-wrappers, LLVM tools,
+/// etc.
+pub fn make_target_bin_path(sysroot: &Path, target_triple: &str) -> PathBuf {
+    let rustlib_path = rustc_target::target_rustlib_path(sysroot, target_triple);
+    PathBuf::from_iter([sysroot, Path::new(&rustlib_path), Path::new("bin")])
+}
+
 #[cfg(unix)]
 fn current_dll_path() -> Result<PathBuf, String> {
     use std::ffi::{CStr, OsStr};
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index db01bb90d6fac..05284c5ed40e9 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -449,15 +449,24 @@ impl Session {
         )
     }
 
-    /// Returns a list of directories where target-specific tool binaries are located.
+    /// Returns a list of directories where target-specific tool binaries are located. Some fallback
+    /// directories are also returned, for example if `--sysroot` is used but tools are missing
+    /// (#125246): we also add the bin directories to the sysroot where rustc is located.
     pub fn get_tools_search_paths(&self, self_contained: bool) -> Vec<PathBuf> {
-        let rustlib_path = rustc_target::target_rustlib_path(&self.sysroot, config::host_triple());
-        let p = PathBuf::from_iter([
-            Path::new(&self.sysroot),
-            Path::new(&rustlib_path),
-            Path::new("bin"),
-        ]);
-        if self_contained { vec![p.clone(), p.join("self-contained")] } else { vec![p] }
+        let bin_path = filesearch::make_target_bin_path(&self.sysroot, config::host_triple());
+        let fallback_sysroot_paths = filesearch::sysroot_candidates()
+            .into_iter()
+            .map(|sysroot| filesearch::make_target_bin_path(&sysroot, config::host_triple()));
+        let search_paths = std::iter::once(bin_path).chain(fallback_sysroot_paths);
+
+        if self_contained {
+            // The self-contained tools are expected to be e.g. in `bin/self-contained` in the
+            // sysroot's `rustlib` path, so we add such a subfolder to the bin path, and the
+            // fallback paths.
+            search_paths.flat_map(|path| [path.clone(), path.join("self-contained")]).collect()
+        } else {
+            search_paths.collect()
+        }
     }
 
     pub fn init_incr_comp_session(&self, session_dir: PathBuf, lock_file: flock::Lock) {