diff --git a/Documentation/config/worktree.txt b/Documentation/config/worktree.txt
index 048e349482df6c892055720eb53cdcd6c327b6ed..5e35c7d018aecdedca0642b11e45df6d19024d42 100644
--- a/Documentation/config/worktree.txt
+++ b/Documentation/config/worktree.txt
@@ -7,3 +7,13 @@ worktree.guessRemote::
 	such a branch exists, it is checked out and set as "upstream"
 	for the new branch.  If no such match can be found, it falls
 	back to creating a new branch from the current HEAD.
+
+worktree.useRelativePaths::
+	Link worktrees using relative paths (when "true") or absolute
+	paths (when "false"). This is particularly useful for setups
+	where the repository and worktrees may be moved between
+	different locations or environments. Defaults to "false".
++
+Note that setting `worktree.useRelativePaths` to "true" implies enabling the
+`extension.relativeWorktrees` config (see linkgit:git-config[1]),
+thus making it incompatible with older versions of Git.
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 70437c815f13852bd2eb862176b8b933e6de0acf..60a671bbc255aa7763b4f77511c09f6a02783e00 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -216,6 +216,11 @@ To remove a locked worktree, specify `--force` twice.
 This can also be set up as the default behaviour by using the
 `worktree.guessRemote` config option.
 
+--[no-]relative-paths::
+	Link worktrees using relative paths or absolute paths (default).
+	Overrides the `worktree.useRelativePaths` config option, see
+	linkgit:git-config[1].
+
 --[no-]track::
 	When creating a new branch, if `<commit-ish>` is a branch,
 	mark it as "upstream" from the new branch.  This is the
diff --git a/builtin/worktree.c b/builtin/worktree.c
index dae63dedf4cac2621f51f95a39aa456b33acd894..e3b4a71ee0bc13d5e817cf7dcc398e9e2bd975de 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -120,12 +120,14 @@ struct add_opts {
 	int quiet;
 	int checkout;
 	int orphan;
+	int relative_paths;
 	const char *keep_locked;
 };
 
 static int show_only;
 static int verbose;
 static int guess_remote;
+static int use_relative_paths;
 static timestamp_t expire;
 
 static int git_worktree_config(const char *var, const char *value,
@@ -134,6 +136,9 @@ static int git_worktree_config(const char *var, const char *value,
 	if (!strcmp(var, "worktree.guessremote")) {
 		guess_remote = git_config_bool(var, value);
 		return 0;
+	} else if (!strcmp(var, "worktree.userelativepaths")) {
+		use_relative_paths = git_config_bool(var, value);
+		return 0;
 	}
 
 	return git_default_config(var, value, ctx, cb);
@@ -414,8 +419,7 @@ static int add_worktree(const char *path, const char *refname,
 			const struct add_opts *opts)
 {
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
-	struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT;
-	struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT;
 	const char *name;
 	struct strvec child_env = STRVEC_INIT;
 	unsigned int counter = 0;
@@ -491,10 +495,7 @@ static int add_worktree(const char *path, const char *refname,
 
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-	strbuf_realpath(&sb_path_realpath, path, 1);
-	strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1);
-	write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp));
-	write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp));
+	write_worktree_linking_files(sb_git, sb, opts->relative_paths);
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
@@ -578,12 +579,9 @@ static int add_worktree(const char *path, const char *refname,
 
 	strvec_clear(&child_env);
 	strbuf_release(&sb);
-	strbuf_release(&sb_tmp);
 	strbuf_release(&symref);
 	strbuf_release(&sb_repo);
-	strbuf_release(&sb_repo_realpath);
 	strbuf_release(&sb_git);
-	strbuf_release(&sb_path_realpath);
 	strbuf_release(&sb_name);
 	free_worktree(wt);
 	return ret;
@@ -796,12 +794,15 @@ static int add(int ac, const char **av, const char *prefix)
 			     PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
 		OPT_BOOL(0, "guess-remote", &guess_remote,
 			 N_("try to match the new branch name with a remote-tracking branch")),
+		OPT_BOOL(0, "relative-paths", &opts.relative_paths,
+			 N_("use relative paths for worktrees")),
 		OPT_END()
 	};
 	int ret;
 
 	memset(&opts, 0, sizeof(opts));
 	opts.checkout = 1;
+	opts.relative_paths = use_relative_paths;
 	ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);
 	if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
 		die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach");
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index cfc4aeb1798c6d023909cec771e5b74e983af5ea..fc1a909123e7932194ac433d6b5e194a26ed8233 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -1207,4 +1207,49 @@ test_expect_success '"add" with initialized submodule, with submodule.recurse se
 	git -C project-clone -c submodule.recurse worktree add ../project-5
 '
 
+test_expect_success 'can create worktrees with relative paths' '
+	test_when_finished "git worktree remove relative" &&
+	test_config worktree.useRelativePaths false &&
+	git worktree add --relative-paths ./relative &&
+	echo "gitdir: ../.git/worktrees/relative" >expect &&
+	test_cmp expect relative/.git &&
+	echo "../../../relative/.git" >expect &&
+	test_cmp expect .git/worktrees/relative/gitdir
+'
+
+test_expect_success 'can create worktrees with absolute paths' '
+	test_config worktree.useRelativePaths true &&
+	git worktree add ./relative &&
+	echo "gitdir: ../.git/worktrees/relative" >expect &&
+	test_cmp expect relative/.git &&
+	git worktree add --no-relative-paths ./absolute &&
+	echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
+	test_cmp expect absolute/.git &&
+	echo "$(pwd)/absolute/.git" >expect &&
+	test_cmp expect .git/worktrees/absolute/gitdir
+'
+
+test_expect_success 'move repo without breaking relative internal links' '
+	test_when_finished rm -rf repo moved &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		git worktree add --relative-paths wt1 &&
+		cd .. &&
+		mv repo moved &&
+		cd moved/wt1 &&
+		git worktree list >out 2>err &&
+		test_must_be_empty err
+	)
+'
+
+test_expect_success 'relative worktree sets extension config' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	git -C repo commit --allow-empty -m base &&
+	git -C repo worktree add --relative-paths ./foo &&
+	test_cmp_config -C repo 1 core.repositoryformatversion
+'
+
 test_done
diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh
index 976d048e3efc74be9cd909ce76d552b3944d2e10..5eb52b9abbf29514dc082c260ebb7a5e8e63aae0 100755
--- a/t/t2401-worktree-prune.sh
+++ b/t/t2401-worktree-prune.sh
@@ -120,11 +120,12 @@ test_expect_success 'prune duplicate (main/linked)' '
 	! test -d .git/worktrees/wt
 '
 
-test_expect_success 'not prune proper worktrees when run inside linked worktree' '
+test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' '
 	test_when_finished rm -rf repo wt_ext &&
 	git init repo &&
 	(
 	    cd repo &&
+	    git config worktree.useRelativePaths true &&
 	    echo content >file &&
 	    git add file &&
 	    git commit -m msg &&
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index 33ea9cb21ba07c9563530b54da06753eaa993fe2..780daa6cd6351f8fa9434619cc212aade8f01420 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -261,6 +261,7 @@ test_expect_success 'broken main worktree still at the top' '
 '
 
 test_expect_success 'linked worktrees are sorted' '
+	test_when_finished "rm -rf sorted" &&
 	mkdir sorted &&
 	git init sorted/main &&
 	(
@@ -280,6 +281,27 @@ test_expect_success 'linked worktrees are sorted' '
 	test_cmp expected sorted/main/actual
 '
 
+test_expect_success 'linked worktrees with relative paths are shown with absolute paths' '
+	test_when_finished "rm -rf sorted" &&
+	mkdir sorted &&
+	git init sorted/main &&
+	(
+		cd sorted/main &&
+		test_tick &&
+		test_commit new &&
+		git worktree add --relative-paths ../first &&
+		git worktree add ../second &&
+		git worktree list --porcelain >out &&
+		grep ^worktree out >actual
+	) &&
+	cat >expected <<-EOF &&
+	worktree $(pwd)/sorted/main
+	worktree $(pwd)/sorted/first
+	worktree $(pwd)/sorted/second
+	EOF
+	test_cmp expected sorted/main/actual
+'
+
 test_expect_success 'worktree path when called in .git directory' '
 	git worktree list >list1 &&
 	git -C .git worktree list >list2 &&
diff --git a/t/t2408-worktree-relative.sh b/t/t2408-worktree-relative.sh
deleted file mode 100755
index a3136db7e28cb20926ff44211e246ce625a6e51a..0000000000000000000000000000000000000000
--- a/t/t2408-worktree-relative.sh
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/bin/sh
-
-test_description='test worktrees linked with relative paths'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-test_expect_success 'links worktrees with relative paths' '
-	test_when_finished rm -rf repo &&
-	git init repo &&
-	(
-		cd repo &&
-		test_commit initial &&
-		git worktree add wt1 &&
-		echo "../../../wt1/.git" >expected_gitdir &&
-		cat .git/worktrees/wt1/gitdir >actual_gitdir &&
-		echo "gitdir: ../.git/worktrees/wt1" >expected_git &&
-		cat wt1/.git >actual_git &&
-		test_cmp expected_gitdir actual_gitdir &&
-		test_cmp expected_git actual_git
-	)
-'
-
-test_expect_success 'move repo without breaking relative internal links' '
-	test_when_finished rm -rf repo moved &&
-	git init repo &&
-	(
-		cd repo &&
-		test_commit initial &&
-		git worktree add wt1 &&
-		cd .. &&
-		mv repo moved &&
-		cd moved/wt1 &&
-		git status >out 2>err &&
-		test_must_be_empty err
-	)
-'
-
-test_done
