diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt
index 6af6f5edb2..93d6c59fac 100644
--- a/Documentation/config/fetch.txt
+++ b/Documentation/config/fetch.txt
@@ -94,3 +94,7 @@ fetch.writeCommitGraph::
 	merge and the write may take longer. Having an updated commit-graph
 	file helps performance of many Git commands, including `git merge-base`,
 	`git push -f`, and `git log --graph`. Defaults to false.
+
+fetch.updateHead::
+  Defines when to update the remote HEAD symbolic ref. Values are 'never',
+  'missing' (update only when HEAD is missing), and 'always'.
diff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt
index a8e6437a90..905661c7f7 100644
--- a/Documentation/config/remote.txt
+++ b/Documentation/config/remote.txt
@@ -84,3 +84,6 @@ remote.<name>.promisor::
 remote.<name>.partialclonefilter::
 	The filter that will be applied when fetching from this
 	promisor remote.
+
+remote.<name>.updateHead::
+  Defines when to update the remote HEAD symbolic ref. See `fetch.updateHead`.
diff --git a/builtin/fetch.c b/builtin/fetch.c
index f9c3c49f14..b47b06f001 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -55,6 +55,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
 static int prune_tags = -1; /* unspecified */
 #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 
+static int fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;
+
 static int all, append, dry_run, force, keep, multiple, update_head_ok;
 static int write_fetch_head = 1;
 static int verbosity, deepen_relative, set_upstream;
@@ -120,6 +122,9 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(k, "fetch.updatehead"))
+		return parse_update_head(&fetch_update_head, k, v);
+
 	return git_default_config(k, v, cb);
 }
 
@@ -1342,6 +1347,38 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map)
 	}
 }
 
+static void update_head(int config, const struct ref *head, const struct remote *remote)
+{
+	struct strbuf ref = STRBUF_INIT, target = STRBUF_INIT;
+	const char *r, *head_name = NULL;
+
+	if (!head || !head->symref || !remote)
+		return;
+
+	strbuf_addf(&ref, "refs/remotes/%s/HEAD", remote->name);
+	skip_prefix(head->symref, "refs/heads/", &head_name);
+	strbuf_addf(&target, "refs/remotes/%s/%s", remote->name, head_name);
+
+	r = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+		ref.buf, RESOLVE_REF_READING,
+		NULL, NULL);
+
+	if (r) {
+		if (config == FETCH_UPDATE_HEAD_MISSING)
+			/* already present */
+			return;
+		else if (config == FETCH_UPDATE_HEAD_ALWAYS && !strcmp(r, target.buf))
+			/* already up-to-date */
+			return;
+		else
+			/* should never happen */
+			return;
+	}
+
+	if (create_symref(ref.buf, target.buf, "remote update head"))
+		warning(_("could not set remote head"));
+}
+
 static int do_fetch(struct transport *transport,
 		    struct refspec *rs)
 {
@@ -1351,6 +1388,7 @@ static int do_fetch(struct transport *transport,
 	const struct ref *remote_refs;
 	struct strvec ref_prefixes = STRVEC_INIT;
 	int must_list_refs = 1;
+	int need_update_head = 0, update_head_config = 0;
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1382,8 +1420,18 @@ static int do_fetch(struct transport *transport,
 				break;
 			}
 		}
-	} else if (transport->remote && transport->remote->fetch.nr)
+	} else if (transport->remote && transport->remote->fetch.nr) {
+		if (transport->remote->update_head)
+			update_head_config = transport->remote->update_head;
+		else
+			update_head_config = fetch_update_head;
+
+		need_update_head = update_head_config && update_head_config != FETCH_UPDATE_HEAD_NEVER;
+
+		if (need_update_head)
+			strvec_push(&ref_prefixes, "HEAD");
 		refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);
+	}
 
 	if (tags == TAGS_SET || tags == TAGS_DEFAULT) {
 		must_list_refs = 1;
@@ -1427,6 +1475,9 @@ static int do_fetch(struct transport *transport,
 		goto cleanup;
 	}
 
+	if (need_update_head)
+		update_head(update_head_config, find_ref_by_name(remote_refs, "HEAD"), transport->remote);
+
 	if (set_upstream) {
 		struct branch *branch = branch_get("HEAD");
 		struct ref *rm;
diff --git a/remote.c b/remote.c
index 8a6dbbb903..dad8062561 100644
--- a/remote.c
+++ b/remote.c
@@ -298,6 +298,25 @@ static void read_branches_file(struct remote *remote)
 	remote->fetch_tags = 1; /* always auto-follow */
 }
 
+int parse_update_head(int *r, const char *var, const char *value)
+{
+	if (!r)
+		return -1;
+	else if (!value)
+		return config_error_nonbool(var);
+	else if (!strcmp(value, "never"))
+		*r = FETCH_UPDATE_HEAD_NEVER;
+	else if (!strcmp(value, "missing"))
+		*r = FETCH_UPDATE_HEAD_MISSING;
+	else if (!strcmp(value, "always"))
+		*r = FETCH_UPDATE_HEAD_ALWAYS;
+	else {
+		error(_("malformed value for %s: %s"), var, value);
+		return error(_("must be one of never, missing, or always"));
+	}
+	return 0;
+}
+
 static int handle_config(const char *key, const char *value, void *cb)
 {
 	const char *name;
@@ -418,6 +437,8 @@ static int handle_config(const char *key, const char *value, void *cb)
 					 key, value);
 	} else if (!strcmp(subkey, "vcs")) {
 		return git_config_string(&remote->foreign_vcs, key, value);
+	} else if (!strcmp(subkey, "updatehead")) {
+		return parse_update_head(&remote->update_head, key, value);
 	}
 	return 0;
 }
diff --git a/remote.h b/remote.h
index 3211abdf05..c16a2d7b2e 100644
--- a/remote.h
+++ b/remote.h
@@ -21,6 +21,13 @@ enum {
 	REMOTE_BRANCHES
 };
 
+enum {
+	FETCH_UPDATE_HEAD_DEFAULT = 0,
+	FETCH_UPDATE_HEAD_NEVER,
+	FETCH_UPDATE_HEAD_MISSING,
+	FETCH_UPDATE_HEAD_ALWAYS,
+};
+
 struct remote {
 	struct hashmap_entry ent;
 
@@ -62,6 +69,8 @@ struct remote {
 	int prune;
 	int prune_tags;
 
+	int update_head;
+
 	/**
 	 * The configured helper programs to run on the remote side, for
 	 * Git-native protocols.
@@ -372,4 +381,6 @@ int parseopt_push_cas_option(const struct option *, const char *arg, int unset);
 int is_empty_cas(const struct push_cas_option *);
 void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
 
+int parse_update_head(int *r, const char *var, const char *value);
+
 #endif
