diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b6dc27f123465..003c1e5d7ebbe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -122,6 +122,9 @@ jobs:
           # which then uses log commands to actually set them.
           EXTRA_VARIABLES: ${{ toJson(matrix.env) }}
 
+      - name: setup upstream remote
+        run: src/ci/scripts/setup-upstream-remote.sh
+
       - name: ensure the channel matches the target branch
         run: src/ci/scripts/verify-channel.sh
 
diff --git a/src/ci/scripts/setup-upstream-remote.sh b/src/ci/scripts/setup-upstream-remote.sh
new file mode 100755
index 0000000000000..52b4c98a89016
--- /dev/null
+++ b/src/ci/scripts/setup-upstream-remote.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# In CI environments, bootstrap is forced to use the remote upstream based
+# on "git_repository" and "nightly_branch" values from src/stage0 file.
+# This script configures the remote as it may not exist by default.
+
+set -euo pipefail
+IFS=$'\n\t'
+
+ci_dir=$(cd $(dirname $0) && pwd)/..
+source "$ci_dir/shared.sh"
+
+git_repository=$(parse_stage0_file_by_key "git_repository")
+nightly_branch=$(parse_stage0_file_by_key "nightly_branch")
+
+# Configure "rust-lang/rust" upstream remote only when it's not origin.
+if [ -z "$(git config remote.origin.url | grep $git_repository)" ]; then
+    echo "Configuring https://github.com/$git_repository remote as upstream."
+    git remote add upstream "https://github.com/$git_repository"
+    REMOTE_NAME="upstream"
+else
+    REMOTE_NAME="origin"
+fi
+
+git fetch $REMOTE_NAME $nightly_branch
diff --git a/src/ci/shared.sh b/src/ci/shared.sh
index 2b0a10e4d08d9..1e6a008a5de81 100644
--- a/src/ci/shared.sh
+++ b/src/ci/shared.sh
@@ -136,3 +136,15 @@ function releaseChannel {
         echo $RUST_CI_OVERRIDE_RELEASE_CHANNEL
     fi
 }
+
+# Parse values from src/stage0 file by key
+function parse_stage0_file_by_key {
+    local key="$1"
+    local file="$ci_dir/../stage0"
+    local value=$(awk -F= '{a[$1]=$2} END {print(a["'$key'"])}' $file)
+    if [ -z "$value" ]; then
+        echo "ERROR: Key '$key' not found in '$file'."
+        exit 1
+    fi
+    echo "$value"
+}
diff --git a/src/tools/build_helper/src/git.rs b/src/tools/build_helper/src/git.rs
index 10c5476cd8f3b..cac5a990a732e 100644
--- a/src/tools/build_helper/src/git.rs
+++ b/src/tools/build_helper/src/git.rs
@@ -1,6 +1,8 @@
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 
+use crate::ci::CiEnv;
+
 pub struct GitConfig<'a> {
     pub git_repository: &'a str,
     pub nightly_branch: &'a str,
@@ -114,8 +116,8 @@ fn git_upstream_merge_base(
 
 /// Searches for the nearest merge commit in the repository that also exists upstream.
 ///
-/// If it fails to find the upstream remote, it then looks for the most recent commit made
-/// by the merge bot by matching the author's email address with the merge bot's email.
+/// It looks for the most recent commit made by the merge bot by matching the author's email
+/// address with the merge bot's email.
 pub fn get_closest_merge_commit(
     git_dir: Option<&Path>,
     config: &GitConfig<'_>,
@@ -127,7 +129,15 @@ pub fn get_closest_merge_commit(
         git.current_dir(git_dir);
     }
 
-    let merge_base = git_upstream_merge_base(config, git_dir).unwrap_or_else(|_| "HEAD".into());
+    let merge_base = {
+        if CiEnv::is_ci() {
+            git_upstream_merge_base(config, git_dir).unwrap()
+        } else {
+            // For non-CI environments, ignore rust-lang/rust upstream as it usually gets
+            // outdated very quickly.
+            "HEAD".to_string()
+        }
+    };
 
     git.args([
         "rev-list",