diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py
index c24c68e2bc36ccceefc30336711cfe1ce0730ceb..bae4fe79b8de067fc2fe6630861d06b6b42f35e4 100644
--- a/pmb/config/__init__.py
+++ b/pmb/config/__init__.py
@@ -46,7 +46,7 @@ pmaports_min_version = "7"
 # Version of the work folder (as asked during 'pmbootstrap init'). Increase
 # this number, whenever migration is required and provide the migration code,
 # see migrate_work_folder()).
-work_version = 7
+work_version = 8
 
 # Minimum required version of postmarketos-ondev (pmbootstrap install --ondev).
 # Try to support the current versions of all channels (edge, v21.03). When
diff --git a/pmb/helpers/git.py b/pmb/helpers/git.py
index bef4e70923c099ffb8abbeb0da58bf07b07ae4f5..c478a6f5a1bc819ee4db2c72258b7418182e8b8c 100644
--- a/pmb/helpers/git.py
+++ b/pmb/helpers/git.py
@@ -1,9 +1,11 @@
 # Copyright 2024 Oliver Smith
 # SPDX-License-Identifier: GPL-3.0-or-later
 import configparser
+from enum import Enum
 from pathlib import Path
+from typing import Final
 from pmb.core.context import get_context
-from pmb.core.pkgrepo import pkgrepo_path
+from pmb.core.pkgrepo import pkgrepo_default_path, pkgrepo_path
 from pmb.helpers import logging
 import os
 import re
@@ -15,6 +17,7 @@ import pmb.helpers.pmaports
 import pmb.helpers.run
 from pmb.helpers.exceptions import NonBugError
 from pmb.meta import Cache
+from pmb.types import PathString
 
 re_branch_aports = re.compile(r"^\d+\.\d\d+-stable$")
 re_branch_pmaports = re.compile(r"^v\d\d\.\d\d$")
@@ -94,6 +97,12 @@ def clean_worktree(path: Path):
     return pmb.helpers.run.user_output(command, path) == ""
 
 
+def list_remotes(aports: Path) -> list[str]:
+    command = ["git", "remote", "-v"]
+    output = pmb.helpers.run.user_output(command, aports, output="null")
+    return output.splitlines()
+
+
 def get_upstream_remote(aports: Path):
     """Find the remote, which matches the git URL from the config.
 
@@ -101,17 +110,83 @@ def get_upstream_remote(aports: Path):
     """
     name_repo = aports.parts[-1]
     urls = pmb.config.git_repos[name_repo]
-    command = ["git", "remote", "-v"]
-    output = pmb.helpers.run.user_output(command, aports, output="null")
-    for line in output.split("\n"):
+    lines = list_remotes(aports)
+    for line in lines:
         if any(u in line for u in urls):
             return line.split("\t", 1)[0]
+
     raise NonBugError(
         f"{name_repo}: could not find remote name for any URL '{urls}' in git"
         f" repository: {aports}"
     )
 
 
+class RemoteType(Enum):
+    FETCH = "fetch"
+    PUSH = "push"
+
+    @staticmethod
+    def from_git_output(git_remote_type: str) -> "RemoteType":
+        match git_remote_type:
+            case "(fetch)":
+                return RemoteType.FETCH
+            case "(push)":
+                return RemoteType.PUSH
+            case _:
+                raise ValueError(f'Unknown remote type "{git_remote_type}"')
+
+
+def set_remote_url(repo: Path, remote_name: str, remote_url: str, remote_type: RemoteType) -> None:
+    command: list[PathString] = [
+        "git",
+        "-C",
+        repo,
+        "remote",
+        "set-url",
+        remote_name,
+        remote_url,
+        "--push" if remote_type == RemoteType.PUSH else "--no-push",
+    ]
+
+    pmb.helpers.run.user(command, output="stdout")
+
+
+# Intentionally lower case for case-insensitive comparison
+OUTDATED_GIT_REMOTES_HTTP: Final[list[str]] = ["https://gitlab.com/postmarketos/pmaports.git"]
+OUTDATED_GIT_REMOTES_SSH: Final[list[str]] = ["git@gitlab.com:postmarketos/pmaports.git"]
+
+
+def migrate_upstream_remote() -> None:
+    """Migrate pmaports git remote URL from gitlab.com to gitlab.postmarketos.org."""
+
+    repo = pkgrepo_default_path()
+    repo_name = repo.parts[-1]
+    lines = list_remotes(repo)
+
+    current_git_remote_http: Final[str] = pmb.config.git_repos[repo_name][0]
+    current_git_remote_ssh: Final[str] = pmb.config.git_repos[repo_name][1]
+
+    for line in lines:
+        if not line:
+            continue  # Skip empty line at the end.
+
+        remote_name, remote_url, remote_type_raw = line.split()
+        remote_type = RemoteType.from_git_output(remote_type_raw)
+
+        if remote_url.lower() in OUTDATED_GIT_REMOTES_HTTP:
+            new_remote = current_git_remote_http
+        elif remote_url.lower() in OUTDATED_GIT_REMOTES_SSH:
+            new_remote = current_git_remote_ssh
+        else:
+            new_remote = None
+
+        if new_remote:
+            logging.info(
+                f"Migrating to new {remote_type.value} URL (from {remote_url} to {new_remote})"
+            )
+            set_remote_url(repo, remote_name, current_git_remote_http, remote_type)
+
+
 @Cache("aports")
 def parse_channels_cfg(aports: Path):
     """Parse channels.cfg from pmaports.git, origin/master branch.
diff --git a/pmb/helpers/other.py b/pmb/helpers/other.py
index 70e332e4afbe5b44aeec3964dcf3a028ea686754..1c2402dfa5b246e7e77027aacd07596768c9ec31 100644
--- a/pmb/helpers/other.py
+++ b/pmb/helpers/other.py
@@ -1,6 +1,7 @@
 # Copyright 2023 Oliver Smith
 # SPDX-License-Identifier: GPL-3.0-or-later
 from pmb.core.context import get_context
+from pmb.core.pkgrepo import pkgrepo_default_path
 from pmb.helpers import logging
 import os
 from pathlib import Path
@@ -111,6 +112,30 @@ def migrate_work_folder():
         migrate_success(context.config.work, 7)
         current = 7
 
+    if current == 7:
+        # Ask for confirmation
+        logging.info("Changelog:")
+        logging.info("* Moved from GitLab.com to gitlab.postmarketOS.org")
+        logging.info("Migration will do the following:")
+        logging.info("* Update your pmaports remote URL")
+        if not pmb.helpers.cli.confirm():
+            raise RuntimeError("Aborted.")
+
+        pmb.helpers.git.migrate_upstream_remote()
+        try:
+            pmb.helpers.git.get_upstream_remote(pkgrepo_default_path())
+        except NonBugError:
+            logging.error(
+                "Couldn't find new upstream remote, migration probably failed."
+                " Please try updating the remote manually with:\n"
+                f" $ git -C '{pkgrepo_default_path()}' remote set-url origin 'https://gitlab.postmarketos.org/postmarketOS/pmaports.git'"
+            )
+            raise NonBugError("Migration failed.")
+
+        # Update version file
+        migrate_success(context.config.work, 8)
+        current = 8
+
     # Can't migrate, user must delete it
     if current != required:
         raise NonBugError(