--- a/aptsources/distro.py	2026-09-12 13:56:30.778094603 +0000
+++ b/aptsources/distro.py	2026-09-12 13:56:30.779094593 +0000
@@ -28,6 +28,7 @@
 import os
 import platform
 import re
+from urllib.parse import urlparse
 from xml.etree.ElementTree import ElementTree
 
 from apt_pkg import gettext as _
@@ -414,6 +415,20 @@
                 if len(source.comps) < 1:
                     self.sourceslist.remove(source)
 
+    def _archive_root(self, uri):
+        """Return ``host/first_path_segment`` of a base URI.
+
+        Used to decide whether two repositories live on the same archive
+        (e.g. Ubuntu's ``-security`` shares the main archive) or on a
+        separate one (e.g. Debian's ``debian-security``).
+        """
+        if not uri:
+            return ""
+        parsed = urlparse(uri)
+        segments = [seg for seg in parsed.path.split("/") if seg]
+        first = segments[0] if segments else ""
+        return (parsed.netloc + "/" + first).lower()
+
     def change_server(self, uri):
         """Change the server of all distro specific sources to
         a given host"""
@@ -429,19 +444,36 @@
             if len(source.comps) < 1:
                 self.sourceslist.remove(source)
 
+        def separate_archive(source):
+            # A child source lives on a separate archive from the main one
+            # (e.g. Debian's debian-security). Such sources must keep their
+            # own archive when the main download server is changed,
+            # otherwise they end up pointing at a non-existent path and
+            # break ``apt update`` (Debian #1038747).
+            child_base = getattr(source.template, "base_uri", None)
+            if not child_base:
+                return False
+            return self._archive_root(child_base) != self._archive_root(
+                self.source_template.base_uri
+            )
+
         seen_binary = []
         seen_source = []
         self.default_server = uri
         for source in self.main_sources:
             change_server_of_source(source, uri, seen_binary)
         for source in self.child_sources:
-            # Do not change the forces server of a child source
-            if (
-                source.template.base_uri is None
-                or source.template.base_uri != source.uri
-            ):
-                change_server_of_source(source, uri, seen_binary)
+            # Do not change the server of a child source that lives on its
+            # own (separate) archive, see above.
+            if separate_archive(source):
+                continue
+            change_server_of_source(source, uri, seen_binary)
         for source in self.source_code_sources:
+            # Likewise keep deb-src entries that belong to a separate child
+            # archive (e.g. Debian debian-security deb-src).
+            parent = getattr(source, "parent", None)
+            if parent is not None and separate_archive(parent):
+                continue
             change_server_of_source(source, uri, seen_source)
 
     def is_codename(self, name):
