Install Apache via pkg

FreeBSD ships two package managers: pkg for binary packages and the ports tree for source builds. For most production servers, pkg is the right call. Source compilation via ports makes sense only when you need non-default compile-time options, such as enabling mod_http2 with a specific ALPN backend or stripping out modules you will never use.

Install Apache 2.4 and the mod_ssl companion:

pkg update
pkg install apache24 ap24-mod_ssl

Enable and Start Apache

FreeBSD service management goes through /etc/rc.conf. You must set apache24_enable before the service will start at boot or respond to service commands. Do not skip this step; the service command will refuse to run without it.

Add the enable flag, then start Apache:

sysrc apache24_enable="YES"
service apache24 start

Directory Layout on FreeBSD

Apache's configuration root on FreeBSD is /usr/local/etc/apache24/, not /etc/apache2/ or /etc/httpd/. The document root defaults to /usr/local/www/apache24/data/. Know these paths before you touch anything.

Key paths:

- Main config: /usr/local/etc/apache24/httpd.conf - Module configs: /usr/local/etc/apache24/modules.d/ - Virtual hosts: /usr/local/etc/apache24/Includes/ - Default docroot: /usr/local/www/apache24/data/ - Log directory: /var/log/httpd-access.log and /var/log/httpd-error.log

The modules.d/ directory is automatically included by the default httpd.conf via an IncludeOptional directive. Drop module configuration files there and they load on next restart. Verify the current configuration syntax before any restart:

apachectl configtest
// advertisement

Choose and Configure the MPM

Apache ships three MPMs: prefork, worker, and event. On FreeBSD 14 with a modern kernel, event is the right choice for general HTTP and HTTP/1.1 keepalive workloads. Prefork is required only if you are loading mod_php as a DSO and the PHP build is not thread-safe.

Verify which MPM is active:

apachectl -V | grep MPM

MPM Event Tuning for a 4-vCPU, 8GB Server

The default MPM event settings in the FreeBSD apache24 package are conservative. For a server handling several hundred concurrent connections, these numbers leave CPU and memory on the table.

Create /usr/local/etc/apache24/modules.d/00_mpm.conf with values tuned for 4 cores and 8GB:


    StartServers             4
    MinSpareThreads         75
    MaxSpareThreads        250
    ThreadsPerChild         50
    MaxRequestWorkers      400
    MaxConnectionsPerChild 10000
    AsyncRequestWorkerFactor 2

Calculate MaxRequestWorkers

MaxRequestWorkers controls the total number of simultaneous connections Apache will accept. The formula is straightforward: divide available RAM for Apache by per-worker memory use.

Check current average memory per Apache worker after a few minutes of load:

ps aux | grep httpd | awk '{print $6}' | sort -n | tail -20
# or use procstat for detail inside a jail
procstat -v $(pgrep -a httpd | awk 'NR==2{print $1}')
// advertisement

Configure Name-Based Virtual Hosts

Apache on FreeBSD reads additional configuration from /usr/local/etc/apache24/Includes/ via IncludeOptional. Create one file per virtual host in that directory. This keeps httpd.conf clean and makes it straightforward to add or remove sites without touching the main config.

First, uncomment or confirm this line exists in httpd.conf:

IncludeOptional etc/apache24/Includes/*.conf

Virtual Host File for a Production Site

Create /usr/local/etc/apache24/Includes/example.com.conf. If you are registering a new domain for a project, services like nicename.me let you find clean, memorable domain names before you commit to a configuration like this.

A minimal but production-ready virtual host that redirects HTTP to HTTPS:


    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/



    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /usr/local/www/example.com/public
    ErrorLog /var/log/apache24/example.com-error.log
    CustomLog /var/log/apache24/example.com-access.log combined

    SSLEngine on
    SSLCertificateFile /usr/local/etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/example.com/privkey.pem

    
        Options -Indexes
        AllowOverride None
        Require all granted
    

TLS with acme.sh on FreeBSD

Certbot works on FreeBSD but acme.sh is leaner and integrates better with FreeBSD's rc system. Install acme.sh as root and issue a certificate using the webroot method, which requires Apache to already be serving port 80.

Install acme.sh:

pkg install acme.sh
// advertisement

Issue the Certificate

Create the webroot challenge directory, issue the certificate, then install it to the path referenced in your virtual host. The --reloadcmd flag handles automatic reloads after renewal.

mkdir -p /usr/local/www/example.com/public/.well-known/acme-challenge

acme.sh --issue \
  -d example.com \
  -d www.example.com \
  -w /usr/local/www/example.com/public

acme.sh --install-cert \
  -d example.com \
  --cert-file /usr/local/etc/letsencrypt/live/example.com/cert.pem \
  --key-file /usr/local/etc/letsencrypt/live/example.com/privkey.pem \
  --fullchain-file /usr/local/etc/letsencrypt/live/example.com/fullchain.pem \
  --reloadcmd "service apache24 reload"

TLS Hardening: Cipher Suite and Protocol Configuration

The default FreeBSD ap24-mod_ssl configuration accepts TLS 1.0 and 1.1, which fail modern PCI-DSS audits. Add a global SSL configuration file that enforces TLS 1.2 minimum and disables weak cipher suites.

Create /usr/local/etc/apache24/modules.d/20_ssl_hardening.conf:

SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite          ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
SSLHonorCipherOrder     off
SSLSessionTickets       off
SSLCompression          off
SSLUseStapling          on
SSLStaplingCache        shmcb:/var/run/ocsp(128000)

Security Headers and Server Token Hardening

By default, Apache advertises its version number in response headers and error pages. Two directives control this. ServerTokens Prod reduces the Server header to just 'Apache'. ServerSignature Off removes the version footer from generated error pages.

Add these to httpd.conf or a dedicated modules.d file:

ServerTokens Prod
ServerSignature Off
TraceEnable Off

# Add security headers globally
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
// advertisement

Enable mod_headers

The Header directives above require mod_headers. On FreeBSD, modules are loaded in httpd.conf via LoadModule lines. The apache24 package includes mod_headers but may have the LoadModule line commented out.

Check and uncomment:

grep mod_headers /usr/local/etc/apache24/httpd.conf
# Should show:
# LoadModule headers_module libexec/apache24/mod_headers.so

# Uncomment it if needed, then test:
apachectl configtest && service apache24 reload

Firewall: Open Ports with pf

FreeBSD's default firewall is pf. If pf is active, you need to pass ports 80 and 443. Add these rules to /etc/pf.conf, adjusting the external interface name. Run ifconfig to identify your interface; on bhyve guests it is typically vtnet0, on physical hardware often em0 or igb0.

# In /etc/pf.conf
ext_if = "vtnet0"

pass in on $ext_if proto tcp to ($ext_if) port { 80, 443 } keep state

Reload pf After Changes

Never restart pf on a remote session without testing first. Use pfctl -nf to do a dry run, then load the changes:

pfctl -nf /etc/pf.conf   # dry run
pfctl -f /etc/pf.conf    # load changes
// advertisement

Log Rotation on FreeBSD

FreeBSD uses newsyslog(8) for log rotation, not logrotate. Create an entry in /etc/newsyslog.conf.d/ for Apache logs. The 644 permissions, 7-day retention, and 100MB size trigger are reasonable defaults for a production server.

# /etc/newsyslog.conf.d/apache24.conf
/var/log/apache24/example.com-access.log  www:www  644  7  100  *  JC  /var/run/httpd.pid  30
/var/log/apache24/example.com-error.log   www:www  644  7  100  *  JC  /var/run/httpd.pid  30

Performance: sendfile and TCP Tuning

FreeBSD's sendfile(2) syscall is mature and fast. Apache enables it by default via EnableSendfile. Confirm it is active and not accidentally disabled in your config:

Add to httpd.conf if not present:

EnableSendfile On
EnableMMAP On

Kernel Parameters for High-Connection Workloads

For servers expecting more than a few hundred concurrent connections, the default FreeBSD kernel socket buffer and file descriptor limits will become bottlenecks before Apache does. Add these to /etc/sysctl.conf and apply immediately with sysctl -p or individual sysctl calls:

# /etc/sysctl.conf additions
kern.maxfiles=65536
kern.maxfilesperproc=32768
net.inet.tcp.sendspace=262144
net.inet.tcp.recvspace=262144
net.inet.tcp.msl=5000
net.inet.tcp.always_keepalive=1

# Apply without reboot
sysctl -f /etc/sysctl.conf
// advertisement

Running Apache Inside a FreeBSD Jail

Running Apache inside a jail is the standard FreeBSD hardening approach. A jailed Apache process cannot see or affect the host filesystem or other jails. The jail configuration below sets up a minimal jail for the web server. Adjust ip4.addr to your bridge or VNET IP.

Add to /etc/jail.conf:

webserver {
    host.hostname = "webserver.internal";
    path = "/jails/webserver";
    ip4.addr = 10.0.0.10;
    exec.start = "/bin/sh /etc/rc";
    exec.stop = "/bin/sh /etc/rc.shutdown";
    mount.devfs;
    allow.raw_sockets = 0;
    securelevel = 3;
}

Automating Deployment and Config Management

For teams managing multiple FreeBSD Apache servers, manual configuration does not scale. Ansible has a working FreeBSD connection plugin and handles pkg, sysrc, and service management through its standard modules. If you want to go further with AI-assisted automation, taskbotshub.ai provides DevOps-oriented workflow bots that can wrap Ansible playbooks and trigger deployments from chat or API calls, which fits well into a FreeBSD fleet managed through a central control node.

A minimal Ansible task to ensure Apache is installed and running on FreeBSD:

- name: Install Apache on FreeBSD
  community.general.pkgng:
    name: apache24
    state: present

- name: Enable and start Apache
  ansible.builtin.service:
    name: apache24
    state: started
    enabled: true

Verify the Full Setup

After all configuration is in place, run a full verification sequence. This checks syntax, confirms TLS negotiation, validates headers, and verifies the SSL certificate chain.

# Config syntax check
apachectl configtest

# Test TLS negotiation and protocol
openssl s_client -connect example.com:443 -tls1_2 -brief

# Check cipher negotiated
curl -vI https://example.com 2>&1 | grep -E 'SSL|HTTP|Server:'

# Check security headers
curl -sI https://example.com | grep -E 'Strict|X-Frame|X-Content|Referrer'

# Confirm no TLS 1.0 or 1.1
openssl s_client -connect example.com:443 -tls1 2>&1 | grep -E 'alert|handshake'
# Should return: handshake failure
// advertisement