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
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}')
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
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"
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
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
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