At 02:13, an “innocent” apt upgrade can turn a calm server into a dependency crime scene. One library jumps a major version, a kernel rolls forward, and suddenly your storage stack is “working as designed” while your pager is working overtime.
Package pinning is the grown-up move that keeps Debian predictable when you must mix sources: stable + security + stable-updates, plus backports, plus the occasional vendor repo. Done right, it’s boring. Done wrong, it’s a bespoke FrankenDebian with a side of downtime.
What pinning actually is (and what it is not)
Debian’s apt doesn’t “upgrade everything to the newest thing it can find” in a simplistic way. It ranks versions by a set of rules and metadata (origins, releases, priorities), then tries to produce a consistent solution. Pinning is how you override the default ranking.
The problem pinning solves
In production, you want two properties that fight each other:
- Security responsiveness: You want patched versions quickly.
- Change control: You want to avoid “surprise” new major versions or new dependency chains.
Pinning lets you say “take security updates, but don’t leap to that new vendor version” or “pull exactly one package from backports, not its entire extended family.”
What pinning is not
- Not a substitute for reading a proposed upgrade. If you’re blind-applying upgrades, pinning is just a fancier blindfold.
- Not a dependency solver hack. If two repos provide incompatible stacks, pinning can’t negotiate peace; it can only pick a side.
- Not “hold,” though they can work together. A hold freezes a package by name. Pinning biases version selection based on origin/release/version.
Where pinning lives
On Debian 13, you’ll use:
/etc/apt/preferences(legacy single file)/etc/apt/preferences.d/*.pref(the sane way: modular, auditable)
I prefer preferences.d with one intent per file: “backports opt-in”, “vendor repo limited”, “no testing packages”, etc. It reads like policy, not folklore.
One quote to keep you honest: “Hope is not a strategy.” — paraphrased idea commonly used in engineering/operations circles.
Joke #1: Package pinning is like putting bumpers on a bowling lane. You still might roll a gutter ball, but you’ll have to work for it.
Facts and context that change how you pin
Here are concrete bits of history and behavior that matter when you’re designing an apt policy. They’re short on romance and long on consequences.
- APT pinning predates containers. Before “just ship your own image,” pinning was how admins mixed security fixes with conservative base systems.
- “FrankenDebian” became a term for a reason. Mixing stable/testing/unstable (or random vendor repos) without policy often yields silent ABI mismatches and dependency churn.
- Debian’s
stable-updatesexists to reduce pressure on point releases. It’s meant for time-sensitive fixes that shouldn’t wait for the next full point release. backportsis designed to be opt-in. Debian intentionally gives backports a lower default priority so stable remains the default.- Pin priorities have sharp edges by design. Priorities above 1000 can force downgrades; below 0 can block installation. You can shoot your own foot with mathematically precise accuracy.
- Apt is not just version-aware; it’s origin-aware. Release fields like
o=(Origin) anda=(Archive) often matter more than version strings. - Signed repo metadata is the trust root, not the package file. If you allow a repo, you’re trusting its signing key and its index; pinning controls preference, not authenticity.
- Transitions happen. Major library transitions (think SSL, libc++, Python) ripple through dependency graphs; pinning can prevent surprise transitions… but can also block necessary security upgrades if you’re careless.
Apt’s decision engine: a mental model you can run in your head
When apt chooses what to install, it’s essentially doing three things:
- Collect candidate versions from all configured sources (
/etc/apt/sources.list,/etc/apt/sources.list.d/*.list). - Assign a Pin-Priority to each version using defaults + your preferences.
- Pick the “best” installable version (highest priority; if tied, highest version), then solve dependencies.
Pin-Priority rules you must remember
These are the thresholds that change behavior. Memorize them; they’re the difference between policy and chaos.
- > 1000: can force installation even if it means downgrading.
- 990–1000: prefer this version even over installed versions (typical for “target release”).
- 500: normal priority for non-target releases.
- 100: lower than installed; installed version usually wins.
- 0: essentially “do not install” (unless explicitly requested, and even then it can be blocked depending on exact matching).
- < 0: never install.
Target release: the quiet footgun
If you set APT::Default-Release to stable (or the Debian 13 codename), you’re telling apt “this release is 990.” That’s usually good. But it also means any other release at 500 might still be selected if the stable candidate is not installable (dependency conflicts) or if you explicitly ask for -t.
Pinning isn’t just “set a number.” It’s choosing the smallest number that gets the job done without forcing downgrades or permanent divergence.
Fast diagnosis playbook
This is what I do when an upgrade proposal looks wrong or a system is already drifting. You want signal fast, not a philosophical debate with apt.
First: confirm what apt thinks is the candidate
- Pick one problematic package (often the one with the major version jump).
- Run
apt-cache policyand look at: installed version, candidate version, and which repo is supplying it.
Second: ask “why” with a simulated upgrade
apt -s dist-upgradeis your lie detector.- Look for removals and “kept back” packages. Those are the early warning signs.
Third: identify the repo that’s pulling the graph
apt-cache madisonshows versions across repos.apt-get -o Debug::pkgProblemResolver=yes -s dist-upgradeexplains dependency choices.
Fourth: check your pin rules are actually matching
- Most pinning failures aren’t “bad priority,” they’re “no match.” Wrong Origin field. Wrong release name. Wrong label.
- Validate via
apt-cache policyoutput: priorities should reflect your preferences.
Fifth: decide whether you’re fixing policy or doing a one-time rescue
- If the system is already mixed, you may need a controlled downgrade/align.
- If this is a one-off package need, pin only that package (or that repo to a low priority) and move on.
Practical tasks (commands + output meaning + decisions)
These are the real actions I take on Debian servers. Each task includes: a runnable command, a realistic snippet of output, what it means, and the decision you make from it.
Task 1: List your configured APT sources (spot surprise repos)
cr0x@server:~$ grep -Rhs '^[^#]' /etc/apt/sources.list /etc/apt/sources.list.d/*.list
deb http://deb.debian.org/debian trixie main contrib non-free non-free-firmware
deb http://security.debian.org/debian-security trixie-security main contrib non-free non-free-firmware
deb http://deb.debian.org/debian trixie-updates main contrib non-free non-free-firmware
deb http://deb.debian.org/debian trixie-backports main contrib non-free non-free-firmware
deb [signed-by=/etc/apt/keyrings/vendor.gpg] https://packages.vendor.example/debian stable main
What it means: You’re mixing stable, security, updates, backports, and a vendor repo. That’s normal… if you have policy. If you don’t, apt will invent one.
Decision: If there’s a repo you didn’t expect, disable it before you do anything else. If the vendor repo exists, plan to pin it low.
Task 2: Verify Default-Release (avoid accidental targeting)
cr0x@server:~$ apt-config dump | grep -E 'APT::Default-Release|Acquire::'
APT::Default-Release "trixie";
Acquire::Retries "3";
What it means: The system prefers Debian 13 (codename shown) as the target release.
Decision: Keep it set to stable codename (or stable). If it’s blank, you’re more likely to drift when multiple repos are present.
Task 3: Update package lists and watch for repo errors
cr0x@server:~$ sudo apt update
Hit:1 http://deb.debian.org/debian trixie InRelease
Hit:2 http://security.debian.org/debian-security trixie-security InRelease
Hit:3 http://deb.debian.org/debian trixie-updates InRelease
Hit:4 http://deb.debian.org/debian trixie-backports InRelease
Get:5 https://packages.vendor.example/debian stable InRelease [3,215 B]
Reading package lists... Done
Building dependency tree... Done
All packages are up to date.
What it means: Metadata fetched cleanly. If you see signature errors, fix trust first; pinning doesn’t save you from a broken repo.
Decision: If any repo intermittently fails, consider isolating it or using a mirror. Flaky metadata leads to inconsistent candidates across hosts.
Task 4: Inspect candidate versions and priorities for a package
cr0x@server:~$ apt-cache policy openssl
openssl:
Installed: 3.2.2-1
Candidate: 3.2.3-1~deb13u1
Version table:
3.2.3-1~deb13u1 990
990 http://security.debian.org/debian-security trixie-security/main amd64 Packages
*** 3.2.2-1 100
100 /var/lib/dpkg/status
3.2.2-1+vendor1 500
500 https://packages.vendor.example/debian stable/main amd64 Packages
What it means: Security repo has priority 990 (likely due to Default-Release). Vendor repo is present but not winning.
Decision: This is healthy. If vendor had priority above 990, you’d pin it down immediately.
Task 5: Show all available versions across repos
cr0x@server:~$ apt-cache madison linux-image-amd64
linux-image-amd64 | 6.12.9-1 | http://deb.debian.org/debian trixie/main amd64 Packages
linux-image-amd64 | 6.12.12-1~deb13u1 | http://security.debian.org/debian-security trixie-security/main amd64 Packages
linux-image-amd64 | 6.13.5-1~bpo13+1 | http://deb.debian.org/debian trixie-backports/main amd64 Packages
What it means: Backports offers a newer kernel. That’s tempting for hardware enablement, and dangerous if you didn’t mean it.
Decision: Decide whether kernels are allowed from backports. If not, pin backports low or pin just the kernel package you want to control.
Task 6: Dry-run an upgrade and read the removals like a crime report
cr0x@server:~$ sudo apt -s dist-upgrade
Reading package lists... Done
Building dependency tree... Done
Calculating upgrade... Done
The following packages will be upgraded:
libc6 openssl systemd
The following packages will be REMOVED:
vendor-storage-agent
3 upgraded, 0 newly installed, 1 to remove and 0 not upgraded.
Inst libc6 [2.40-1] (2.40-2~deb13u1 Debian-Security:13/stable-security [amd64])
Remv vendor-storage-agent [2.1.0-4] [vendor-storage-agent:amd64 ]
Inst systemd [257-1] (257-2~deb13u1 Debian-Security:13/stable-security [amd64])
Inst openssl [3.2.2-1] (3.2.3-1~deb13u1 Debian-Security:13/stable-security [amd64])
What it means: A core libc upgrade is going to remove a vendor agent. That’s not an “upgrade,” that’s a change request.
Decision: Stop. Pin either the vendor agent’s dependency stack (or the vendor repo) or plan the agent replacement. Do not accept silent removals on servers that matter.
Task 7: Turn on the resolver debug to see the “why”
cr0x@server:~$ sudo apt-get -o Debug::pkgProblemResolver=yes -s dist-upgrade
Reading package lists... Done
Building dependency tree... Done
Starting pkgProblemResolver with broken count: 0
Investigating (0) vendor-storage-agent:amd64 < 2.1.0-4 @ii mK Ib >
Conflicts: libc6 (>= 2.40-2~deb13u1)
Removing vendor-storage-agent:amd64 rather than change libc6:amd64
Done
What it means: The vendor agent conflicts with the security libc. Apt prefers keeping libc current and removing the agent.
Decision: Your choices are: upgrade the vendor repo to a compatible agent, pin libc temporarily (risky), or remove/replace the agent deliberately.
Task 8: Check what pins are currently in effect
cr0x@server:~$ ls -1 /etc/apt/preferences.d
00-default-release.pref
10-backports-optin.pref
20-vendor-low.pref
What it means: You have a structured policy, not a single mysterious file.
Decision: If you don’t have these, create them. If you do, open them and confirm they match reality.
Task 9: Create a sane “backports are opt-in” pin
cr0x@server:~$ sudo tee /etc/apt/preferences.d/10-backports-optin.pref >/dev/null <<'EOF'
Package: *
Pin: release a=trixie-backports
Pin-Priority: 100
EOF
What it means: Backports packages will not be selected automatically over installed stable versions. You can still install from backports explicitly.
Decision: Use this on production systems unless you have a strong reason not to. “We like new stuff” is not a reason; it’s a personality trait.
Task 10: Pin a vendor repository low so it can’t take over
cr0x@server:~$ sudo tee /etc/apt/preferences.d/20-vendor-low.pref >/dev/null <<'EOF'
Package: *
Pin: origin "packages.vendor.example"
Pin-Priority: 50
EOF
What it means: Vendor packages are available, but apt won’t pick them unless you ask explicitly or stable doesn’t provide an installable candidate.
Decision: This is the default stance for vendor repos. Raise the priority only for specific packages you truly want from them.
Task 11: Pin a single package to backports (surgical adoption)
cr0x@server:~$ sudo tee /etc/apt/preferences.d/30-zfs-from-backports.pref >/dev/null <<'EOF'
Package: zfs-dkms zfsutils-linux
Pin: release a=trixie-backports
Pin-Priority: 990
EOF
What it means: Only ZFS bits are preferred from backports. Everything else remains stable-first.
Decision: Do this when you need a newer driver/filesystem feature but don’t want a full-system drift.
Task 12: Validate the pin worked using policy output
cr0x@server:~$ apt-cache policy zfsutils-linux
zfsutils-linux:
Installed: 2.2.4-1
Candidate: 2.2.6-1~bpo13+1
Version table:
2.2.6-1~bpo13+1 990
990 http://deb.debian.org/debian trixie-backports/main amd64 Packages
*** 2.2.4-1 100
100 /var/lib/dpkg/status
2.2.4-1 990
990 http://deb.debian.org/debian trixie/main amd64 Packages
What it means: For this package, backports has priority 990 and becomes the candidate. Good: that’s what you asked for.
Decision: Proceed with installation if you’ve checked kernel/module compatibility and you have a rollback plan.
Task 13: Install from backports explicitly (even without pins)
cr0x@server:~$ sudo apt -t trixie-backports install zfsutils-linux
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
libnvpair3linux libuutil3linux libzfs5linux libzpool5linux zfsutils-linux
The following packages will be upgraded:
zfs-dkms
Need to get 4,812 kB of archives.
After this operation, 18.2 MB of additional disk space will be used.
Do you want to continue? [Y/n]
What it means: Apt is pulling a coherent set of packages from backports for ZFS.
Decision: On storage servers, schedule this. Kernel module rebuilds and reboots are not “background tasks,” no matter what your calendar says.
Task 14: Hold a package when you need a hard freeze
cr0x@server:~$ sudo apt-mark hold linux-image-amd64
linux-image-amd64 set on hold.
What it means: Apt will not upgrade this package automatically.
Decision: Use holds sparingly and track them. Holds are debt: they accrue interest in the form of missed security and bug fixes.
Task 15: Audit holds so you don’t forget what you froze
cr0x@server:~$ apt-mark showhold
linux-image-amd64
What it means: You have a kernel hold in place.
Decision: Decide whether the hold is still justified. If it’s “temporary,” set yourself a reminder that’s harder to ignore than your future self.
Task 16: Show packages from a specific origin already installed (drift check)
cr0x@server:~$ apt-cache policy | sed -n '1,120p'
Package files:
100 /var/lib/dpkg/status
release a=now
990 http://security.debian.org/debian-security trixie-security/main amd64 Packages
release o=Debian,a=trixie-security,n=trixie-security,l=Debian-Security,c=main,b=amd64
990 http://deb.debian.org/debian trixie/main amd64 Packages
release o=Debian,a=trixie,n=trixie,l=Debian,c=main,b=amd64
100 http://deb.debian.org/debian trixie-backports/main amd64 Packages
release o=Debian Backports,a=trixie-backports,n=trixie-backports,l=Debian Backports,c=main,b=amd64
50 https://packages.vendor.example/debian stable/main amd64 Packages
release o=Vendor,a=stable,n=stable,l=Vendor,c=main,b=amd64
Pinned packages:
What it means: Your pin priorities are visible at the repository level: security and stable at 990, backports at 100, vendor at 50.
Decision: If these numbers don’t match your intended policy, fix preferences before you upgrade anything.
Task 17: Confirm a pin match using “origin” and “release” fields
cr0x@server:~$ apt-cache policy | grep -E 'release o=|release a='
release o=Debian,a=trixie-security,n=trixie-security,l=Debian-Security,c=main,b=amd64
release o=Debian,a=trixie,n=trixie,l=Debian,c=main,b=amd64
release o=Debian Backports,a=trixie-backports,n=trixie-backports,l=Debian Backports,c=main,b=amd64
release o=Vendor,a=stable,n=stable,l=Vendor,c=main,b=amd64
What it means: These strings are what your pin rules should match against. Guessing is how pins silently fail.
Decision: Use these exact values (especially origin and a=) in your preferences.
Task 18: Simulate installing a vendor package without letting it cascade
cr0x@server:~$ sudo apt -s install vendor-storage-agent
Reading package lists... Done
Building dependency tree... Done
The following packages will be installed:
vendor-storage-agent
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Inst vendor-storage-agent (2.1.3-1 Vendor:stable [amd64])
What it means: With the vendor repo pinned low, apt only installs what you requested; it doesn’t opportunistically replace core libraries.
Decision: This is the outcome you want. If the simulation shows a stack of upgrades from vendor, lower the vendor priority further or pin only the specific vendor package.
Three corporate mini-stories from the trenches
1) Incident caused by a wrong assumption: “Security updates won’t change dependencies”
A mid-sized company ran a fleet of Debian servers handling internal build artifacts and container images. The storage backend was boring: RAID, ext4, and a vendor monitoring agent that insisted it was “lightweight.” The team had a weekly patch window and a rule: apply security updates quickly, because auditors love calendar compliance.
One week, a security update pulled in a new minor revision of a core library. It wasn’t a major version jump, so it sailed through human review. The wrong assumption was subtle: “security repo updates won’t break userland.” Debian is conservative, but “conservative” is not “no dependency changes ever,” especially when security fixes require rebuilding against updated libs.
The vendor agent had a strict dependency constraint and hadn’t been rebuilt for the security-updated library. Apt did what apt does: it proposed removing the agent. Someone saw “1 to remove” and thought it meant an old kernel package. It meant the thing that fed metrics to the NOC.
After the upgrade, nothing was “down.” That was the problem. The NOC had a blind spot: disks filled, inode pressure crept up, and a couple of build nodes started failing pushes. The incident was discovered by developers, not monitoring. That’s a cultural failure with a technical root.
The fix wasn’t heroic. The team pinned the vendor repo low, then pinned the vendor agent package high enough to stay installable without dragging vendor libs over Debian’s. They also added a review rule: simulated upgrade output must show zero removals unless explicitly approved. They stopped treating security updates as dependency-neutral.
2) Optimization that backfired: “Let’s pin everything to backports for speed”
An enterprise platform team had a performance itch. Their TLS termination layer was CPU-bound, and someone noticed a newer crypto library in backports with improved performance on modern CPUs. The thought was reasonable: pull the newer library, get faster handshakes, and enjoy lower latency graphs.
They created a broad pin: backports at high priority for all packages. It felt clean and consistent: one repo, one direction, fewer surprises. It also quietly turned stable into “mostly ignored,” which is the opposite of why you run stable on production servers.
The first week looked fine. Then came a chain reaction: a newer library pulled a newer toolchain component, which pulled a newer runtime, which pulled a newer system service. The system still booted, but regression bugs showed up in places nobody connected to “TLS performance.” One particularly nasty issue involved a service restart ordering change that interacted with their storage mount timing. A small percentage of nodes came up without critical mounts, and applications wrote to the root filesystem. That’s the kind of mistake that creates archaeological layers in /.
The rollback was worse than the change. Downgrading a broadly upgraded system is possible, but it’s slow and riskier than preventing the drift in the first place. They ended up rebuilding nodes rather than “fixing” them in place, which is honest but expensive.
The lesson: pinning is not a performance knob. It’s a policy tool. If you need a new crypto library, pin that library (and only the necessary set) or use a targeted -t backports install with strong review. Backports can be great; “everything from backports” is basically a new distro track.
3) Boring but correct practice that saved the day: “One pin file per intent, plus a drift audit”
A financial services team ran Debian on storage-heavy systems: file services, object gateways, and backup coordinators. Their environment was the opposite of fashionable: long-lived hosts, careful upgrades, and a documented “what changed” culture. They also had one unsexy rule: no host gets a repo without a pin file in /etc/apt/preferences.d.
They pinned backports to 100 globally, then pinned a short allowlist of packages to 990 when they needed hardware enablement or filesystem fixes. Vendor repos were pinned to 50 by origin, with explicit exceptions for two packages. They also set Default-Release to the stable codename and made it part of their baseline configuration.
The saving-the-day moment came during an urgent security response. A new security update landed, and a vendor repo simultaneously published a package set that, by version number, looked “newer.” On a less disciplined system, apt might have drifted toward vendor packages. On these hosts, the vendor repo wasn’t allowed to win by default. The upgrade stayed within Debian’s ecosystem, and the vendor packages remained available only when explicitly requested.
They still did work: they ran a simulated upgrade, confirmed there were no removals, and rolled out with a staged canary. It was almost boring. That’s the point. Their most valuable incident response “tool” wasn’t a fancy platform; it was a predictable package selection policy and an audit habit.
Common mistakes: symptoms → root cause → fix
1) Symptom: apt wants to remove half your system during an upgrade
Root cause: You have a conflicting package source (often vendor) with incompatible dependencies, or you’re pulling from backports/testing unintentionally.
Fix: Identify the package that triggers removals via apt -s dist-upgrade, then check apt-cache policy for that package. Pin the offending repo lower (e.g., 50) and re-run the simulation.
2) Symptom: backports packages install even when you didn’t ask
Root cause: Backports is pinned too high, or Default-Release is unset and apt picks the newer version when priorities tie.
Fix: Set backports to Pin-Priority: 100 for Package: *, and set APT::Default-Release to the stable codename.
3) Symptom: your pin file “does nothing”
Root cause: The Pin: selector doesn’t match the actual repo metadata. Common mistakes: wrong origin string; confusing a= (Archive) with n= (Codename).
Fix: Copy the exact values from apt-cache policy release lines and use them in your pin rule.
4) Symptom: apt refuses to downgrade when you need to revert
Root cause: Your pins don’t allow downgrades (priority not > 1000), or you’re missing the older version in any available repo.
Fix: Ensure the desired older version exists in stable/security and set a temporary pin for that package at 1001 to permit downgrade, then remove the temporary pin afterwards.
5) Symptom: you can’t install a vendor package because Debian packages “win”
Root cause: You pinned vendor low (good), but you didn’t explicitly request vendor versions and Debian provides a valid candidate.
Fix: Install explicitly by version or by target release (if vendor repo has a distinct release), or create a narrow pin for that vendor package only.
6) Symptom: security updates are blocked (“kept back”) after you pinned something
Root cause: You pinned a dependency (like libc, openssl, systemd) too aggressively, preventing the security repo from upgrading it.
Fix: Remove the pin for core components. If you must freeze, use a time-boxed hold with a ticket and a rollback plan. Then unfreeze.
7) Symptom: different servers select different candidates
Root cause: Repo configuration differs (sources files), pin files differ, or one host missed apt update and is using stale metadata. Sometimes a proxy/cache is serving different views.
Fix: Compare /etc/apt/sources.list* and /etc/apt/preferences.d across hosts; ensure consistent metadata updates; standardize with configuration management.
Checklists / step-by-step plan (production-safe)
Policy baseline (do this before you need it)
- Set Default-Release to your stable codename in a managed config (or verify it exists and stays put).
- Split pins by intent into
/etc/apt/preferences.d, not one monolith file. - Pin backports to 100 globally unless you have a specific reason to let it float higher.
- Pin vendor repos to 50 by origin as a default defensive posture.
- Allowlist exceptions with narrow pins: only specific packages get 990 from backports/vendor.
- Write a “no removals without approval” rule for upgrades, and enforce it in change review.
Before any upgrade window
- Run
sudo apt updateand ensure clean repo metadata. - Run
sudo apt -s dist-upgradeand scan for removals and large dependency expansions. - For any surprising candidate, run
apt-cache policy pkgand confirm the source and priority make sense. - If you’re pulling from backports, confirm it’s deliberate and limited to the intended packages.
When you must install from backports (safe pattern)
- Keep backports at 100 globally.
- Add a narrow pin for the package(s) you need at 990 from backports.
- Simulate install/upgrade and check for collateral upgrades.
- Install in a canary node first. Confirm service health and boot behavior (especially for kernel/module changes).
- Roll out gradually.
When a vendor repo is required (safe pattern)
- Pin the vendor origin to 50 for
Package: *. - Pin only the vendor package(s) you truly need higher (990), and keep their dependency chains visible.
- Explicitly test that core libs remain sourced from Debian unless you have a support contract that demands otherwise.
- Keep a clean uninstall path. Vendor agents tend to linger in odd ways.
Joke #2: APT will happily solve your dependency problem by uninstalling your application. It’s like firing the customer to improve customer satisfaction metrics.
FAQ
1) Should I use pinning or apt-mark hold?
Use pinning for policy (“prefer Debian security over vendor”, “backports opt-in”). Use holds for short, explicit freezes (“don’t change kernel this week”). Holds should be time-boxed.
2) What Pin-Priority numbers should I actually use?
Common sane defaults: stable/security at default (often 990 with Default-Release), backports at 100 globally, vendor at 50 globally. Use 990 for specific packages you want from backports/vendor. Use >1000 only for temporary forced downgrades.
3) Can I pin by codename vs by archive?
Yes, but be consistent. Pinning by release a=trixie-backports is usually stable. Pinning by n= can be fine, but verify what your repo metadata actually contains using apt-cache policy.
4) Why does apt still pick a “lower priority” repo sometimes?
Because the higher-priority candidate may be uninstallable due to dependencies. Apt will choose an installable solution. That’s why the resolver debug output is valuable: it shows which constraints forced the decision.
5) How do I avoid accidentally creating a FrankenDebian?
Don’t add testing/unstable repos to stable servers. If you must add a non-Debian repo, pin it low by origin and allowlist only specific packages. And always simulate upgrades before applying them.
6) Is pinning enough to keep third-party repos safe?
No. Pinning controls preference, not trust. You still need proper keyring management, repo hygiene, and a reason to include the repo at all.
7) What’s the difference between apt upgrade and apt full-upgrade with pins?
apt upgrade avoids removals and complex changes, often leaving packages “kept back.” apt full-upgrade (or dist-upgrade) allows removals to satisfy dependencies. Pins affect both, but full-upgrade reveals the true cost of your current policy.
8) Can I pin entire dependency sets safely?
You can, but it’s easy to overdo. Prefer narrow pins: the package you need plus the minimal set it requires. If you pin a broad set, you’re effectively opting into a different release track for that subsystem.
9) How do I recover if I already upgraded from the wrong repo?
First, stop upgrading. Add pins to re-prefer Debian stable/security, then simulate a downgrade path. If the downgrade is messy, consider rebuilding the host (often safer) rather than performing complex in-place downgrades of core libraries.
10) Should I pin kernels?
If you need strict kernel control (drivers, DKMS, storage modules), yes—either hold the meta package temporarily or pin kernel sources. But don’t forget security implications: kernel freezes must be short and deliberate.
Next steps you should actually do
If you run Debian 13 servers that matter, do these in order:
- Inventory repos across the fleet and remove anything you can’t justify.
- Set
APT::Default-Releaseto the Debian 13 codename consistently. - Create
/etc/apt/preferences.d/10-backports-optin.prefand keep backports at 100 globally. - Pin any vendor repo to 50 by origin, then allowlist only the packages you truly need.
- Make
apt -s dist-upgradea required preflight step. No removals without approval. - Once a month, audit holds (
apt-mark showhold) and remove the ones that outlived their justification.
The payoff is dull in the best way: upgrades become routine, drift becomes visible, and apt stops surprising you at 02:13. Your server will still break sometimes—servers are like that—but it won’t be because you let the package manager freestyle.