FreeBSD.software
Home/Blog/FreeBSD Package Management: pkg vs Ports Complete Guide
tutorial2026-03-29

FreeBSD Package Management: pkg vs Ports Complete Guide

Complete guide to FreeBSD package management. Covers pkg binary packages, the Ports collection, building from source, Poudriere, repository configuration, and when to use each approach.

# 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.