# 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
sh
pkg install nginx
Output:
Updating 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:
sh
pkg install nginx postgresql16-server redis
Install without confirmation prompts (useful in scripts):
sh
pkg install -y nginx
Searching for Packages
Search by name:
sh
pkg search nginx
nginx-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:
sh
pkg search -D "web server"
Get detailed information about a specific package:
sh
pkg search -f nginx
Querying Installed Packages
List all installed packages:
sh
pkg info
Get details on one package:
sh
pkg info nginx
nginx-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:
sh
pkg info -l nginx
Find which package owns a file:
sh
pkg which /usr/local/sbin/nginx
/usr/local/sbin/nginx was installed by package nginx-1.26.2,3
Deleting Packages
Remove a package:
sh
pkg delete nginx
Remove a package and all packages that depend on it:
sh
pkg delete -R nginx
Remove orphaned dependencies (packages installed as dependencies that are no longer needed):
sh
pkg autoremove
This is the equivalent of apt autoremove. Run it periodically to reclaim disk space.
Upgrading Packages
Update the repository catalog:
sh
pkg update
Upgrade all installed packages:
sh
pkg upgrade
Upgrade a single package (pkg will also upgrade its dependencies if needed):
sh
pkg upgrade nginx
Locking Packages
Prevent a package from being upgraded or removed:
sh
pkg lock nginx
nginx-1.26.2,3: lock this package? [y/N]: y
Locking nginx-1.26.2,3
Unlock it later:
sh
pkg unlock nginx
List all locked packages:
sh
pkg 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:
sh
pkg audit -F
Fetching 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](/blog/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:
sh
pkg -vv | grep url
url : "pkg+http://pkg.FreeBSD.org/FreeBSD:14:amd64/quarterly",
Switching Between Quarterly and Latest
Create a configuration file to switch to latest:
sh
mkdir -p /usr/local/etc/pkg/repos
Create /usr/local/etc/pkg/repos/FreeBSD.conf:
FreeBSD: {
url: "pkg+http://pkg.FreeBSD.org/FreeBSD:14:amd64/latest"
}
Then update and upgrade:
sh
pkg 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](/blog/freebsd-vps-setup/) 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:
FreeBSD: {
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:
sh
pkg install git
git clone https://git.FreeBSD.org/ports.git /usr/ports
To update the ports tree later:
sh
cd /usr/ports && git pull
If you want the quarterly branch (matching the pkg quarterly repo):
sh
git clone -b 2026Q1 https://git.FreeBSD.org/ports.git /usr/ports
Ports Tree Structure
/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:
sh
ls /usr/ports/www/
Search for a port by name:
sh
cd /usr/ports && make search name=nginx
Or by keyword:
sh
cd /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:
sh
cd /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:
sh
cd /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:
[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:
sh
make rmconfig
To see current options without opening the dialog:
sh
make 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):
sh
make -DBATCH install clean
Uninstalling a Port
sh
cd /usr/ports/www/nginx
make deinstall
Or use pkg directly, since ports register with the package database:
sh
pkg delete nginx
Reinstalling or Upgrading a Port
sh
cd /usr/ports/www/nginx
make reinstall clean
For upgrades after updating the ports tree:
sh
cd /usr/ports && git pull
cd /usr/ports/www/nginx
make deinstall reinstall clean
For bulk upgrades of port-installed software, use portmaster or portupgrade:
sh
pkg 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:
sh
cd /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:
sh
cd /usr/ports/devel/py-pip
make -V FLAVORS
py311 py312
Build a specific flavor:
sh
cd /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
1. You install nginx via pkg (compiled with default options).
2. You later build nginx from ports with custom options.
3. The port overwrites the pkg version. So far, so good.
4. 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:
sh
cd /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:
CONSERVATIVE_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
sh
pkg 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:
sh
poudriere jail -c -j 14amd64 -v 14.2-RELEASE -a amd64
Create a ports tree:
sh
poudriere ports -c -p default
Update them later:
sh
poudriere jail -u -j 14amd64
poudriere ports -u -p default
Defining Your Package List
Create a file listing ports to build:
sh
cat > /usr/local/etc/poudriere.d/pkglist <
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:
sh
poudriere 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:
sh
cat > /usr/local/etc/poudriere.d/14amd64-make.conf <
www_nginx_SET=HTTP_SSL HTTPV2 THREADS HTTP_REALIP
www_nginx_UNSET=MAIL DEBUG
EOF
Running a Build
sh
poudriere 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:
sh
poudriere status -j 14amd64
Serving Your Repository
Point a web server at the package directory. With Nginx:
nginx
server {
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:
custom: {
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](/blog/freebsd-jails-guide/) and servers via pkg.
---
Package Auditing and Security
Vulnerability Scanning
The pkg audit command checks installed packages against FreeBSD's VuXML vulnerability database:
sh
pkg 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:
sh
crontab -e
@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:
sh
pkg check -s -a
This computes checksums of all installed files and reports discrepancies. Useful after a suspected compromise or disk corruption.
Verify dependency integrity:
sh
pkg 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.
sh
pkg check -d -a
nginx has a missing dependency: pcre2
Fix:
sh
pkg 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.
nginx-1.26.2,3 is locked and may not be modified
Review your locks:
sh
pkg lock -l
Decide whether to unlock and upgrade, or keep the lock and skip:
sh
pkg unlock nginx
pkg upgrade
pkg lock nginx
Repository Catalog Errors
Symptom: pkg update fails with checksum or signature errors.
Force a fresh catalog download:
sh
pkg update -f
If the problem persists, check your repository configuration:
sh
pkg -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:
sh
pkg stats
Local 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:
sh
pkg query '%sb %n' | sort -rn | head -20
Remove cached package files:
sh
pkg 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:
sh
pkg clean -a
Fixing a Corrupt Package Database
If the database at /var/db/pkg/local.sqlite is corrupted:
sh
pkg -f update
In extreme cases, back up the file and rebuild:
sh
cp /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](/blog/freebsd-jails-guide/) and want a unified package source
- You want reproducible builds
- You want to test port upgrades before deploying to production
General Recommendations
1. **Stick to one approach per machine.** Use pkg everywhere, or use Poudriere everywhere. Avoid ad-hoc port builds on production systems.
2. **Lock packages you build from ports.** If you must mix, pkg lock is your safety net.
3. **Run pkg audit -F weekly at minimum.** Automate it. Unpatched packages are the leading cause of server compromises.
4. **Use quarterly on production, latest on dev.** The quarterly branch exists for a reason.
5. **Clean up regularly.** Run pkg autoremove and pkg clean after upgrades. Orphaned dependencies and cached packages consume disk over time.
6. **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.
7. **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.
8. **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](/blog/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](/blog/freebsd-jails-guide/) 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:
sh
pkg 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.