FreeBSD Package Management: pkg vs Ports Complete Guide
FreeBSD gives you two ways to install software: pkg, which installs precompiled binary packages, and the Ports collection, which compiles software from source with full control over build options. Most administrators use both, depending on the situation.
Quick Answer: Which Should You Use?
Use pkg if you want fast installs with sane defaults. It handles dependencies automatically, downloads precompiled binaries, and works the way apt or dnf work on Linux. For the vast majority of software on a typical server or workstation, pkg is the right choice.
Use Ports when you need to change compile-time options that pkg does not expose. Common cases include enabling a specific database backend in a web framework, compiling Nginx with non-default modules, building software with custom optimization flags, or when you need a version newer than what the package repository offers.
Use Poudriere when you need custom-compiled packages across multiple machines. It builds packages from ports in isolated jails and produces a proper pkg repository you can point all your servers at.
The rest of this guide covers each approach in depth, including the commands you will use daily, repository configuration, how to mix the two systems safely, and how to set up Poudriere for production use.
pkg Fundamentals
The pkg tool is the standard binary package manager for FreeBSD. On a fresh install, it bootstraps itself the first time you run it.
Installing Packages
shpkg install nginx
Output:
shellUpdating FreeBSD repository catalogue... FreeBSD repository is up to date. All repositories are up to date. The following 2 package(s) will be affected (of 0 checked): New packages to be installed: nginx: 1.26.2,3 pcre2: 10.43 Number of packages to be installed: 2 The process will require 5 MiB more space. 2 MiB to be downloaded. Proceed with this action? [y/N]: y [1/2] Fetching nginx-1.26.2,3.pkg... done [2/2] Fetching pcre2-10.43.pkg... done Checking integrity... done (0 conflicting) [1/2] Installing pcre2-10.43... [1/2] Extracting pcre2-10.43: 100% [2/2] Installing nginx-1.26.2,3... [2/2] Extracting nginx-1.26.2,3: 100%
Install multiple packages at once:
shpkg install nginx postgresql16-server redis
Install without confirmation prompts (useful in scripts):
shpkg install -y nginx
Searching for Packages
Search by name:
shpkg search nginx
shellnginx-1.26.2,3 Web server, reverse proxy nginx-full-1.26.2,3 Web server with many modules nginx-lite-1.26.2,3 Lightweight web server build
Search descriptions:
shpkg search -D "web server"
Get detailed information about a specific package:
shpkg search -f nginx
Querying Installed Packages
List all installed packages:
shpkg info
Get details on one package:
shpkg info nginx
shellnginx-1.26.2,3 Name : nginx Version : 1.26.2,3 Installed on : Sat Mar 28 14:22:10 2026 UTC Origin : www/nginx Architecture : FreeBSD:14:amd64 Prefix : /usr/local Categories : www Licenses : BSD2CLAUSE Maintainer : joneum@FreeBSD.org WWW : https://nginx.org Comment : Robust and small WWW server Options : DSO : on HTTPV2 : on MAIL : on THREADS : on
List files installed by a package:
shpkg info -l nginx
Find which package owns a file:
shpkg which /usr/local/sbin/nginx
shell/usr/local/sbin/nginx was installed by package nginx-1.26.2,3
Deleting Packages
Remove a package:
shpkg delete nginx
Remove a package and all packages that depend on it:
shpkg delete -R nginx
Remove orphaned dependencies (packages installed as dependencies that are no longer needed):
shpkg autoremove
This is the equivalent of apt autoremove. Run it periodically to reclaim disk space.
Upgrading Packages
Update the repository catalog:
shpkg update
Upgrade all installed packages:
shpkg upgrade
Upgrade a single package (pkg will also upgrade its dependencies if needed):
shpkg upgrade nginx
Locking Packages
Prevent a package from being upgraded or removed:
shpkg lock nginx
shellnginx-1.26.2,3: lock this package? [y/N]: y Locking nginx-1.26.2,3
Unlock it later:
shpkg unlock nginx
List all locked packages:
shpkg lock -l
Locking is useful when you have tested a specific version and do not want it changed during a bulk upgrade, or when an upstream update is known to introduce regressions.
Auditing for Vulnerabilities
Check installed packages against the FreeBSD VuXML database:
shpkg audit -F
shellFetching vuln.xml.xz: 100% curl-8.6.0 is vulnerable: curl -- multiple vulnerabilities CVE: CVE-2024-2398 WWW: https://vuxml.freebsd.org/freebsd/abc12345-1234-5678-9abc-def012345678.html 1 problem(s) in 1 of 234 installed package(s) found.
Run pkg audit -F regularly, or automate it with a cron job. On production servers, this is non-negotiable. See our FreeBSD update guide for a complete patching workflow.
Repository Configuration
pkg repositories are configured in /usr/local/etc/pkg/repos/. The default repository ships with FreeBSD at /etc/pkg/FreeBSD.conf, but you override it by creating files in the repos directory.
Quarterly vs Latest
FreeBSD provides two official repository branches:
- Quarterly (default): Updated every quarter. Receives only security fixes and critical updates between branches. Stable and predictable.
- Latest: Tracks the head of the ports tree. Gets new versions as soon as they are committed. More current but occasionally breaks.
Check which branch you are currently using:
shpkg -vv | grep url
shellurl : "pkg+http://pkg.FreeBSD.org/FreeBSD:14:amd64/quarterly",
Switching Between Quarterly and Latest
Create a configuration file to switch to latest:
shmkdir -p /usr/local/etc/pkg/repos
Create /usr/local/etc/pkg/repos/FreeBSD.conf:
shellFreeBSD: { url: "pkg+http://pkg.FreeBSD.org/FreeBSD:14:amd64/latest" }
Then update and upgrade:
shpkg update -f pkg upgrade
The -f flag forces a catalog refresh even if pkg thinks it is current. This is necessary when switching branches because the cached catalog belongs to the old branch.
To switch back to quarterly, change latest back to quarterly in that file, or simply delete the override file.
Recommendation: Use quarterly on production servers. Use latest on development machines where you need newer versions. If you run a FreeBSD VPS for production workloads, quarterly is the safer choice.
Disabling the Default Repository
If you run your own Poudriere repository (covered below), disable the default repo:
shellFreeBSD: { enabled: no }
Then add your custom repository in a separate file.
The Ports Collection Explained
The Ports collection is a set of Makefiles and patches that automate downloading, compiling, and installing third-party software on FreeBSD. It lives in /usr/ports and contains over 34,000 ports organized into categories like www/, databases/, security/, and lang/.
Each port directory contains at minimum:
- Makefile: Build instructions, dependency declarations, version information
- distinfo: Checksums for the source tarball
- pkg-descr: Human-readable description
- pkg-plist: List of files the port installs
Getting the Ports Tree
The recommended method is Git. The old portsnap utility was removed in FreeBSD 14.
Install and fetch with Git:
shpkg install git git clone https://git.FreeBSD.org/ports.git /usr/ports
To update the ports tree later:
shcd /usr/ports && git pull
If you want the quarterly branch (matching the pkg quarterly repo):
shgit clone -b 2026Q1 https://git.FreeBSD.org/ports.git /usr/ports
Ports Tree Structure
shell/usr/ports/ Mk/ # Shared build infrastructure Templates/ # Templates for port Makefiles www/ nginx/ Makefile distinfo pkg-descr pkg-plist files/ # Patches and additional files databases/ postgresql16-server/ redis/ lang/ python311/ ruby33/ ...
Browse available ports:
shls /usr/ports/www/
Search for a port by name:
shcd /usr/ports && make search name=nginx
Or by keyword:
shcd /usr/ports && make search key="reverse proxy"
A faster alternative is the psearch port or simply using pkg search which queries both installed and available packages.
Building from Ports
Basic Build and Install
Navigate to the port directory and run:
shcd /usr/ports/www/nginx make install clean
This downloads the source tarball, verifies checksums, applies FreeBSD-specific patches, compiles the software, installs it, registers it with pkg, and cleans up build files.
Configuring Port Options
Before building, review and change compile-time options:
shcd /usr/ports/www/nginx make config
This opens a dialog (ncurses-based) showing all available options with checkboxes. Options you enable here determine which features are compiled into the binary. For Nginx, you might see options like:
shell[X] DSO Build dynamic modules [X] HTTPV2 Enable HTTP/2 support [ ] MAIL Enable mail proxy [X] THREADS Enable threads support [ ] DEBUG Enable debug logging [ ] GEOIP Enable GeoIP module [X] HTTP_SSL Enable SSL/TLS support [ ] LUA Enable Lua module [ ] NAXSI Enable NAXSI WAF module
To reset options to defaults:
shmake rmconfig
To see current options without opening the dialog:
shmake showconfig
Handling Dependencies
Ports handle dependencies automatically. When you build Nginx with SSL support, it pulls in OpenSSL. When a dependency itself has options, you may be prompted to configure it as well.
Build a port and all dependencies without interactive prompts (uses default options for all dependencies):
shmake -DBATCH install clean
Uninstalling a Port
shcd /usr/ports/www/nginx make deinstall
Or use pkg directly, since ports register with the package database:
shpkg delete nginx
Reinstalling or Upgrading a Port
shcd /usr/ports/www/nginx make reinstall clean
For upgrades after updating the ports tree:
shcd /usr/ports && git pull cd /usr/ports/www/nginx make deinstall reinstall clean
For bulk upgrades of port-installed software, use portmaster or portupgrade:
shpkg install portmaster portmaster -a
Port Options and Flavors
Setting Options Non-Interactively
You can set port options without the dialog by using /etc/make.conf or command-line variables:
In /etc/make.conf:
make# Global options OPTIONS_SET=DOCS EXAMPLES OPTIONS_UNSET=X11 DEBUG # Per-port options www_nginx_SET=HTTP_SSL HTTPV2 THREADS www_nginx_UNSET=MAIL DEBUG GEOIP
The naming convention is category_portname_SET with slashes replaced by underscores.
From the command line:
shcd /usr/ports/www/nginx make OPTIONS_SET="HTTP_SSL HTTPV2" OPTIONS_UNSET="MAIL" install clean
Flavors
Some ports support multiple flavors, typically for different Python or Ruby versions. List available flavors:
shcd /usr/ports/devel/py-pip make -V FLAVORS
shellpy311 py312
Build a specific flavor:
shcd /usr/ports/devel/py-pip make FLAVOR=py312 install clean
Flavors produce different package names (e.g., py311-pip vs py312-pip) and can coexist on the same system.
Mixing pkg and Ports: The Dangers and How to Do It Safely
This is where most FreeBSD newcomers get burned. The pkg database tracks all installed software regardless of whether it was installed via pkg install or make install from ports. The danger is version and option mismatches.
What Goes Wrong
- You install
nginxvia pkg (compiled with default options). - You later build
nginxfrom ports with custom options. - The port overwrites the pkg version. So far, so good.
- You run
pkg upgrade. pkg sees the port-installed version is "different" from the repository version and overwrites your custom build with the default binary package.
Now your custom Nginx modules are gone.
How to Mix Safely
Method 1: Lock port-built packages
After building from ports, immediately lock the package:
shcd /usr/ports/www/nginx make install clean pkg lock nginx
Now pkg upgrade will skip Nginx. You are responsible for upgrading it manually from ports when needed.
Method 2: Use CONSERVATIVE_UPGRADE
In /usr/local/etc/pkg.conf:
shellCONSERVATIVE_UPGRADE: true
This makes pkg less aggressive about replacing packages that were not installed from the repository.
Method 3: Use Poudriere instead
The cleanest approach is to not mix at all. Build your custom packages with Poudriere and serve them from your own repository. Every machine uses pkg exclusively, with your custom options baked in. This is the production-grade solution.
Poudriere: Building Your Own Package Repository
Poudriere is the tool the FreeBSD project itself uses to build the official package sets. It creates isolated jails, builds ports inside them, and produces a pkg repository you can point your servers at.
Installing Poudriere
shpkg install poudriere
Initial Configuration
Edit /usr/local/etc/poudriere.conf:
sh# Where to store jails, ports trees, packages, logs BASEFS=/usr/local/poudriere ZPOOL=zroot FREEBSD_HOST=https://download.FreeBSD.org # Use tmpfs for faster builds (requires enough RAM) USE_TMPFS=yes # Number of parallel build jobs PARALLEL_JOBS=4 # Sign packages (recommended for production) PKG_REPO_SIGNING_KEY=/usr/local/etc/poudriere.d/keys/repo.key
Creating a Jail and Ports Tree
Create a build jail matching your target FreeBSD version:
shpoudriere jail -c -j 14amd64 -v 14.2-RELEASE -a amd64
Create a ports tree:
shpoudriere ports -c -p default
Update them later:
shpoudriere jail -u -j 14amd64 poudriere ports -u -p default
Defining Your Package List
Create a file listing ports to build:
shcat > /usr/local/etc/poudriere.d/pkglist <<EOF www/nginx databases/postgresql16-server databases/redis security/sudo shells/bash editors/vim net/rsync sysutils/tmux EOF
Setting Port Options
Configure options for your builds:
shpoudriere options -j 14amd64 -p default -z default -f /usr/local/etc/poudriere.d/pkglist
This opens the config dialog for each port. Options are saved in /usr/local/etc/poudriere.d/14amd64-default-default-options/.
Or set them in a make.conf for the build:
shcat > /usr/local/etc/poudriere.d/14amd64-make.conf <<EOF www_nginx_SET=HTTP_SSL HTTPV2 THREADS HTTP_REALIP www_nginx_UNSET=MAIL DEBUG EOF
Running a Build
shpoudriere bulk -j 14amd64 -p default -f /usr/local/etc/poudriere.d/pkglist
Poudriere resolves all dependencies, builds everything in dependency order inside the jail, and produces packages in /usr/local/poudriere/data/packages/14amd64-default/.
Monitor the build via the web interface (Poudriere includes a static HTML status page) or watch the logs:
shpoudriere status -j 14amd64
Serving Your Repository
Point a web server at the package directory. With Nginx:
nginxserver { listen 80; server_name pkg.internal.example.com; root /usr/local/poudriere/data/packages/14amd64-default; autoindex on; }
On client machines, create /usr/local/etc/pkg/repos/custom.conf:
shellcustom: { url: "http://pkg.internal.example.com", enabled: yes }
And disable the default repo if you want clients to use only your packages.
This approach scales well. Build once on a dedicated build machine, distribute to all your FreeBSD jails and servers via pkg.
Package Auditing and Security
Vulnerability Scanning
The pkg audit command checks installed packages against FreeBSD's VuXML vulnerability database:
shpkg audit -F
The -F flag fetches the latest database before scanning. Run it without -F to use the cached copy.
Automating Audits
Add to root's crontab:
shcrontab -e
shell@daily /usr/sbin/pkg audit -F -q | mail -s "pkg audit report" admin@example.com
The -q flag suppresses output when there are no vulnerabilities, so you only receive mail when action is required.
Checking Package Integrity
Verify that installed package files have not been modified:
shpkg check -s -a
This computes checksums of all installed files and reports discrepancies. Useful after a suspected compromise or disk corruption.
Verify dependency integrity:
shpkg check -d -a
This detects missing dependencies, which can happen after botched manual deletions.
Troubleshooting Common Issues
Broken Dependencies
Symptom: A program fails to start, complaining about missing shared libraries.
shpkg check -d -a
shellnginx has a missing dependency: pcre2
Fix:
shpkg install -f pcre2
The -f flag forces reinstallation even if pkg thinks the package is already installed.
Locked Packages Blocking Upgrades
Symptom: pkg upgrade fails because a locked package conflicts.
shellnginx-1.26.2,3 is locked and may not be modified
Review your locks:
shpkg lock -l
Decide whether to unlock and upgrade, or keep the lock and skip:
shpkg unlock nginx pkg upgrade pkg lock nginx
Repository Catalog Errors
Symptom: pkg update fails with checksum or signature errors.
Force a fresh catalog download:
shpkg update -f
If the problem persists, check your repository configuration:
shpkg -vv
Verify that the URL is correct and that your system's clock is accurate (TLS certificate validation requires a correct clock).
Disk Space Issues
Check how much space packages consume:
shpkg stats
shellLocal package database: Installed packages: 234 Disk space occupied: 2 GiB Remote package database(s): Number of repositories: 1 Packages available: 33847 Unique packages: 33847 Total size of packages: 58 GiB
Find the largest installed packages:
shpkg query '%sb %n' | sort -rn | head -20
Remove cached package files:
shpkg clean
This deletes downloaded .pkg files from /var/cache/pkg/. It does not touch installed software.
Remove all cached files including those for currently installed versions:
shpkg clean -a
Fixing a Corrupt Package Database
If the database at /var/db/pkg/local.sqlite is corrupted:
shpkg -f update
In extreme cases, back up the file and rebuild:
shcp /var/db/pkg/local.sqlite /var/db/pkg/local.sqlite.bak pkg update -f
Best Practices and Decision Guide
Use pkg When
- You need standard software with default options
- Speed of installation matters (production deployments, CI pipelines)
- You manage many servers and want consistency
- You are on a system with limited CPU or RAM for compiling
Use Ports When
- You need non-default compile-time options (specific modules, backends, optimizations)
- You need a version that is not yet in the pkg repository
- You are developing or patching a port
- You need custom patches applied to upstream source
Use Poudriere When
- You need custom options across a fleet of servers
- You run FreeBSD jails and want a unified package source
- You want reproducible builds
- You want to test port upgrades before deploying to production
General Recommendations
- Stick to one approach per machine. Use pkg everywhere, or use Poudriere everywhere. Avoid ad-hoc port builds on production systems.
- Lock packages you build from ports. If you must mix,
pkg lockis your safety net.
- Run
pkg audit -Fweekly at minimum. Automate it. Unpatched packages are the leading cause of server compromises.
- Use quarterly on production, latest on dev. The quarterly branch exists for a reason.
- Clean up regularly. Run
pkg autoremoveandpkg cleanafter upgrades. Orphaned dependencies and cached packages consume disk over time.
- Document your port options. If you use Poudriere, your make.conf files serve as documentation. If you build ports directly, maintain a record of which ports you built and with what options. Future you will thank present you.
- Keep the ports tree and pkg repo in sync. If you use quarterly pkg, use the quarterly ports branch. Mixing latest ports with quarterly packages causes version conflicts.
- Back up
/var/db/pkg/. The package database is small but critical. Include it in your backup strategy, especially before major upgrades. See our FreeBSD update guide for full system upgrade procedures.
Frequently Asked Questions
Can I use both pkg and ports on the same system?
Yes, and many administrators do. Both systems share the same package database at /var/db/pkg/local.sqlite. The risk is that pkg upgrade may overwrite port-built software with default binary packages. Mitigate this by locking port-built packages with pkg lock, or better yet, use Poudriere to build custom packages and use pkg exclusively.
How do I find out what compile-time options a binary package was built with?
Run pkg info and look at the Options section. This shows which options were enabled when the package was compiled. If you need different options, you must build from ports or use Poudriere.
Is portsnap still available?
No. portsnap was removed from FreeBSD base in FreeBSD 14.0. The recommended way to obtain and update the ports tree is with Git: git clone https://git.FreeBSD.org/ports.git /usr/ports. If you do not want to install Git, you can download a snapshot tarball from the FreeBSD website, but Git is strongly preferred for ongoing maintenance.
How do I upgrade all packages installed from ports?
You have several options. The simplest is portmaster -a, which rebuilds all outdated ports. Alternatively, update the ports tree with git pull, then use portmaster or portupgrade to rebuild. For a cleaner approach, use Poudriere to build updated packages and then run pkg upgrade on the target machine.
What happens if I switch from quarterly to latest?
Running pkg update -f && pkg upgrade after changing your repository URL will upgrade all packages to the latest branch versions. This can be a significant jump. Some packages may have ABI changes that require dependent packages to be reinstalled. pkg handles this automatically in most cases, but review the upgrade plan it shows before confirming. To switch back to quarterly, reverse the configuration change, force-update the catalog, and downgrade (which may require pkg install -f for affected packages since pkg does not natively downgrade).
How often should I run pkg update and pkg upgrade?
On production servers, check for security updates at least weekly with pkg audit -F. Apply security updates promptly. For general upgrades, quarterly branch users can update monthly with relatively low risk. Latest branch users should update more frequently since falling behind increases the chance of difficult upgrade paths. Always test upgrades on a staging system or jail before applying to production.
How do I completely remove a package and all its dependencies?
pkg does not have a single command for this. The workflow is:
shpkg delete nginx pkg autoremove
The delete command removes Nginx. The autoremove command then removes any dependencies that were only installed because of Nginx and are no longer needed by any other package. Always review what autoremove plans to remove before confirming.
Summary
FreeBSD's dual package management system is one of its greatest strengths. pkg provides fast, reliable binary package management for everyday use. The Ports collection gives you complete control over how software is compiled. Poudriere bridges the gap by letting you build custom packages and serve them through standard pkg infrastructure.
For most use cases, start with pkg. When you hit a limitation -- you need a specific module, a non-default backend, or a custom patch -- reach for ports or Poudriere. Keep your systems updated, audit for vulnerabilities regularly, and document any customizations you make.
That combination of speed, flexibility, and control is what makes FreeBSD package management worth learning properly.