diff --git a/Documentation/config/worktree.txt b/Documentation/config/worktree.txt
index 048e349482df6c892055720eb53cdcd6c327b6ed..44b783c2774dc5ff65e3fa232b0c25cd5254876b 100644
--- a/Documentation/config/worktree.txt
+++ b/Documentation/config/worktree.txt
@@ -7,3 +7,8 @@ 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::
+	If set to `true`, worktrees will be linked to the repository using
+	relative paths rather than using absolute paths. This is particularly
+	useful for setups where the repository and worktrees may be moved between
+	different locations or environments.
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 70437c815f13852bd2eb862176b8b933e6de0acf..975dc3c46d480480457ec4857988a6b8bc67b647 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -216,6 +216,18 @@ 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::
+	Worktrees will be linked to the repository using relative paths
+	rather than using absolute paths. This is particularly useful for setups
+	where the repository and worktrees may be moved between different
+	locations or environments.
++
+With `repair`, the linking files will be updated if there's an absolute/relative
+mismatch, even if the links are correct.
++
+This can also be set up as the default behaviour by using the
+`worktree.useRelativePaths` config option.
+
 --[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..c1130be5890c905c0b648782a834eb8dfcd79ba5 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -134,6 +134,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);
@@ -796,6 +799,8 @@ 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", &use_relative_paths,
+			 N_("use relative paths for worktrees")),
 		OPT_END()
 	};
 	int ret;
@@ -1189,6 +1194,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 		OPT__FORCE(&force,
 			 N_("force move even if worktree is dirty or locked"),
 			 PARSE_OPT_NOCOMPLETE),
+		OPT_BOOL(0, "relative-paths", &use_relative_paths,
+			 N_("use relative paths for worktrees")),
 		OPT_END()
 	};
 	struct worktree **worktrees, *wt;
@@ -1382,6 +1389,8 @@ static int repair(int ac, const char **av, const char *prefix)
 	const char **p;
 	const char *self[] = { ".", NULL };
 	struct option options[] = {
+		OPT_BOOL(0, "relative-paths", &use_relative_paths,
+			 N_("use relative paths for worktrees")),
 		OPT_END()
 	};
 	int rc = 0;
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
diff --git a/worktree.c b/worktree.c
index 77ff484d3ec48c547ee4e3d958dfa28a52c1eaa7..de5c5e53a5f2a758ddf470b5d6a9ad6c66247181 100644
--- a/worktree.c
+++ b/worktree.c
@@ -14,6 +14,8 @@
 #include "wt-status.h"
 #include "config.h"
 
+int use_relative_paths = 0;
+
 void free_worktree(struct worktree *worktree)
 {
 	if (!worktree)
@@ -111,9 +113,9 @@ struct worktree *get_linked_worktree(const char *id,
 	strbuf_strip_suffix(&worktree_path, "/.git");
 
 	if (!is_absolute_path(worktree_path.buf)) {
-	    strbuf_strip_suffix(&path, "gitdir");
-	    strbuf_addbuf(&path, &worktree_path);
-	    strbuf_realpath_forgiving(&worktree_path, path.buf, 0);
+		strbuf_strip_suffix(&path, "gitdir");
+		strbuf_addbuf(&path, &worktree_path);
+		strbuf_realpath_forgiving(&worktree_path, path.buf, 0);
 	}
 
 	CALLOC_ARRAY(worktree, 1);
@@ -725,12 +727,15 @@ static int is_main_worktree_path(const char *path)
  * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
  * be able to infer the gitdir by manually reading /path/to/worktree/.git,
  * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
+ *
+ * Returns -1 on failure and strbuf.len on success.
  */
 static int infer_backlink(const char *gitfile, struct strbuf *inferred)
 {
 	struct strbuf actual = STRBUF_INIT;
 	const char *id;
 
+	strbuf_reset(inferred);
 	if (strbuf_read_file(&actual, gitfile, 0) < 0)
 		goto error;
 	if (!starts_with(actual.buf, "gitdir:"))
@@ -741,18 +746,16 @@ static int infer_backlink(const char *gitfile, struct strbuf *inferred)
 	id++; /* advance past '/' to point at <id> */
 	if (!*id)
 		goto error;
-	strbuf_reset(inferred);
 	strbuf_git_common_path(inferred, the_repository, "worktrees/%s", id);
 	if (!is_directory(inferred->buf))
 		goto error;
 
 	strbuf_release(&actual);
-	return 1;
-
+	return inferred->len;
 error:
 	strbuf_release(&actual);
 	strbuf_reset(inferred); /* clear invalid path */
-	return 0;
+	return -1;
 }
 
 /*
diff --git a/worktree.h b/worktree.h
index e96118621638667d87c5d7e0452ed10bd1ddf606..37e65d508ed23d3e7a29850bb938285072a3aaa6 100644
--- a/worktree.h
+++ b/worktree.h
@@ -5,6 +5,8 @@
 
 struct strbuf;
 
+extern int use_relative_paths;
+
 struct worktree {
 	/* The repository this worktree belongs to. */
 	struct repository *repo;
