FreeBSD.software
Home/Guides/How to Set Up WordPress on FreeBSD
tutorial·2026-04-09·10 min read

How to Set Up WordPress on FreeBSD

Install WordPress on FreeBSD with NGINX, PHP-FPM, and MariaDB: complete setup guide including SSL, caching with Redis, security hardening, and automatic updates.

How to Set Up WordPress on FreeBSD

WordPress runs reliably on FreeBSD with NGINX, PHP-FPM, and MariaDB. This combination delivers better performance and tighter security than the typical Linux LAMP stack, thanks to FreeBSD's robust network stack, ZFS storage, and jails for isolation.

This guide walks through the complete setup from package installation to a production-ready WordPress site with SSL, Redis object caching, security hardening, and automated updates. For the underlying NGINX configuration, see the NGINX Production Setup guide.

Prerequisites

  • FreeBSD 14.0 or later
  • Root access
  • A registered domain name pointing to your server's IP address
  • SSL certificates (covered below, or see the Let's Encrypt guide)

Step 1: Install Required Packages

Install NGINX, PHP, MariaDB, and Redis in one command:

sh
pkg install nginx php83 php83-extensions php83-curl php83-gd \ php83-mbstring php83-mysqli php83-xml php83-zip php83-zlib \ php83-opcache php83-pecl-redis php83-intl php83-bcmath \ php83-fileinfo php83-pecl-imagick php83-exif php83-ftp \ php83-session php83-tokenizer php83-ctype php83-dom \ php83-simplexml php83-xmlreader php83-xmlwriter \ mariadb1011-server mariadb1011-client redis

Enable all services at boot:

sh
sysrc nginx_enable="YES" sysrc php_fpm_enable="YES" sysrc mysql_enable="YES" sysrc redis_enable="YES"

Step 2: Configure MariaDB

Start MariaDB and run the secure installation:

sh
service mysql-server start mysql_secure_installation

Follow the prompts: set a root password, remove anonymous users, disable remote root login, and remove the test database.

Create the WordPress database and user:

sh
mysql -u root -p
sql
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'StrongPasswordHere'; GRANT ALL PRIVILEGES ON wordpress.* TO 'wp_user'@'localhost'; FLUSH PRIVILEGES; EXIT;

MariaDB Performance Tuning

Edit /usr/local/etc/mysql/conf.d/server.cnf:

sh
cat > /usr/local/etc/mysql/conf.d/server.cnf << 'EOF' [mysqld] # InnoDB settings innodb_buffer_pool_size = 256M innodb_log_file_size = 64M innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT # Query cache (useful for WordPress) query_cache_type = 1 query_cache_size = 64M query_cache_limit = 2M # Connection limits max_connections = 100 max_allowed_packet = 64M # Temporary tables tmp_table_size = 64M max_heap_table_size = 64M # Thread cache thread_cache_size = 16 # Logging slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow-query.log long_query_time = 2 EOF mkdir -p /var/log/mysql chown mysql:mysql /var/log/mysql service mysql-server restart

Adjust innodb_buffer_pool_size to approximately 50-70% of available RAM on a dedicated database server, or 25% on a shared server.

Step 3: Configure PHP-FPM

Edit the PHP-FPM pool configuration at /usr/local/etc/php-fpm.d/www.conf:

sh
# Key settings to modify: sed -i '' \ -e 's/^listen = .*/listen = \/var\/run\/php-fpm.sock/' \ -e 's/^;listen.owner = .*/listen.owner = www/' \ -e 's/^;listen.group = .*/listen.group = www/' \ -e 's/^;listen.mode = .*/listen.mode = 0660/' \ -e 's/^pm.max_children = .*/pm.max_children = 20/' \ -e 's/^pm.start_servers = .*/pm.start_servers = 5/' \ -e 's/^pm.min_spare_servers = .*/pm.min_spare_servers = 3/' \ -e 's/^pm.max_spare_servers = .*/pm.max_spare_servers = 10/' \ /usr/local/etc/php-fpm.d/www.conf

Create the PHP configuration for WordPress:

sh
cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini

Edit /usr/local/etc/php.ini with WordPress-optimized settings:

sh
cat > /usr/local/etc/php/ext-30-wordpress.ini << 'EOF' ; WordPress-optimized PHP settings upload_max_filesize = 64M post_max_size = 64M memory_limit = 256M max_execution_time = 300 max_input_vars = 3000 date.timezone = UTC ; OPcache settings opcache.enable = 1 opcache.memory_consumption = 128 opcache.interned_strings_buffer = 16 opcache.max_accelerated_files = 10000 opcache.revalidate_freq = 60 opcache.fast_shutdown = 1 EOF

Start PHP-FPM:

sh
service php-fpm start

Step 4: Configure NGINX

Create the NGINX server block for WordPress:

sh
cat > /usr/local/etc/nginx/conf.d/wordpress.conf << 'NGINXEOF' server { listen 80; server_name example.com www.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name example.com www.example.com; root /usr/local/www/wordpress; index index.php index.html; # SSL configuration ssl_certificate /usr/local/etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /usr/local/etc/letsencrypt/live/example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Max upload size client_max_body_size 64M; # Gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_min_length 1000; # WordPress permalink support location / { try_files $uri $uri/ /index.php?$args; } # PHP processing location ~ \.php$ { fastcgi_pass unix:/var/run/php-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_intercept_errors on; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; fastcgi_read_timeout 300; } # Static file caching location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 30d; add_header Cache-Control "public, immutable"; access_log off; } # Deny access to sensitive files location ~ /\.ht { deny all; } location ~ /wp-config\.php { deny all; } location ~ /xmlrpc\.php { deny all; } # Deny access to uploads directory PHP execution location ~* /wp-content/uploads/.*\.php$ { deny all; } # Deny access to wp-includes PHP files location ~* /wp-includes/.*\.php$ { deny all; } } NGINXEOF

Ensure the main NGINX config includes the conf.d directory. Edit /usr/local/etc/nginx/nginx.conf:

sh
# Add inside the http {} block if not already present: # include /usr/local/etc/nginx/conf.d/*.conf;

Test and start NGINX:

sh
nginx -t service nginx start

Step 5: Install SSL with Let's Encrypt

If you do not already have SSL certificates, install them with certbot:

sh
pkg install py311-certbot py311-certbot-nginx # Obtain certificate certbot --nginx -d example.com -d www.example.com

Set up automatic renewal:

sh
# Add to /etc/crontab echo '0 0 1 * * root certbot renew --quiet && service nginx reload' >> /etc/crontab

For detailed SSL configuration, see the Let's Encrypt on FreeBSD guide.

Step 6: Download and Install WordPress

sh
cd /tmp fetch https://wordpress.org/latest.tar.gz tar xzf latest.tar.gz mv wordpress /usr/local/www/wordpress chown -R www:www /usr/local/www/wordpress

Create the WordPress configuration file:

sh
cd /usr/local/www/wordpress cp wp-config-sample.php wp-config.php

Edit wp-config.php with your database credentials:

sh
sed -i '' \ -e "s/database_name_here/wordpress/" \ -e "s/username_here/wp_user/" \ -e "s/password_here/StrongPasswordHere/" \ /usr/local/www/wordpress/wp-config.php

Generate unique authentication keys and salts:

sh
# Fetch fresh keys from the WordPress API fetch -o - https://api.wordpress.org/secret-key/1.1/salt/

Copy the output and replace the placeholder keys in wp-config.php.

Add the following lines before / That's all, stop editing! /:

php
/** Redis object cache */ define('WP_REDIS_HOST', '127.0.0.1'); define('WP_REDIS_PORT', 6379); /** Security hardening */ define('DISALLOW_FILE_EDIT', true); define('WP_AUTO_UPDATE_CORE', 'minor'); /** Optimize for reverse proxy */ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') { $_SERVER['HTTPS'] = 'on'; }

Set proper file permissions:

sh
# Directories: 755, Files: 644 find /usr/local/www/wordpress -type d -exec chmod 755 {} \; find /usr/local/www/wordpress -type f -exec chmod 644 {} \; # wp-config.php should be more restrictive chmod 640 /usr/local/www/wordpress/wp-config.php

Step 7: Configure Redis Object Caching

Redis dramatically reduces database queries by caching WordPress objects in memory.

Configure Redis:

sh
cat >> /usr/local/etc/redis.conf << 'EOF' # WordPress-specific Redis settings maxmemory 128mb maxmemory-policy allkeys-lru EOF service redis start

After completing the WordPress web installation (Step 8), install the Redis Object Cache plugin:

  1. Log into WordPress admin
  2. Go to Plugins > Add New
  3. Search for "Redis Object Cache" by Till Kruess
  4. Install and activate
  5. Go to Settings > Redis and click "Enable Object Cache"

Verify Redis is working:

sh
redis-cli info stats | grep keyspace redis-cli info memory | grep used_memory_human

Step 8: Complete the Web Installation

Open your browser and navigate to https://example.com. WordPress will present the installation wizard:

  1. Choose your language
  2. Enter the site title
  3. Create an admin username (never use "admin")
  4. Set a strong password
  5. Enter your email address
  6. Click "Install WordPress"

Step 9: Security Hardening

Disable XML-RPC

The NGINX config already blocks xmlrpc.php. This prevents brute-force amplification attacks that target this endpoint.

Limit Login Attempts

Install the "Limit Login Attempts Reloaded" plugin from the WordPress admin panel. Configure it to lock accounts after 3 failed attempts.

Set Up Fail2ban for WordPress

sh
pkg install py311-fail2ban sysrc fail2ban_enable="YES"

Create the WordPress jail configuration:

sh
cat > /usr/local/etc/fail2ban/jail.d/wordpress.conf << 'EOF' [wordpress] enabled = true filter = wordpress logpath = /var/log/nginx/access.log maxretry = 3 bantime = 3600 findtime = 600 EOF cat > /usr/local/etc/fail2ban/filter.d/wordpress.conf << 'EOF' [Definition] failregex = ^<HOST> .* "POST /wp-login.php ^<HOST> .* "POST /xmlrpc.php EOF service fail2ban start

File System Permissions

Ensure the web server user cannot modify core files:

sh
# WordPress core files owned by root, readable by www chown -R root:www /usr/local/www/wordpress chmod -R 750 /usr/local/www/wordpress # Only uploads and cache directories writable by www chown -R www:www /usr/local/www/wordpress/wp-content/uploads chown -R www:www /usr/local/www/wordpress/wp-content/cache chmod -R 770 /usr/local/www/wordpress/wp-content/uploads chmod -R 770 /usr/local/www/wordpress/wp-content/cache

This prevents a compromised plugin from modifying core WordPress files.

Step 10: Performance Optimization

NGINX FastCGI Cache

Add a caching layer in NGINX to serve cached pages without hitting PHP at all.

In the http {} block of /usr/local/etc/nginx/nginx.conf:

nginx
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m inactive=60m max_size=512m; fastcgi_cache_key "$scheme$request_method$host$request_uri";

In the server block, add caching directives:

nginx
# Skip cache for logged-in users and POST requests set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($http_cookie ~* "wordpress_logged_in") { set $skip_cache 1; } if ($http_cookie ~* "comment_author") { set $skip_cache 1; } location ~ \.php$ { fastcgi_cache WORDPRESS; fastcgi_cache_valid 200 60m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-Cache-Status $upstream_cache_status; # ... existing fastcgi settings ... }

Create the cache directory:

sh
mkdir -p /var/cache/nginx/wordpress chown www:www /var/cache/nginx/wordpress service nginx reload

WP-Cron via System Cron

Disable WP-Cron's default behavior (runs on every page load) and use system cron instead:

Add to wp-config.php:

php
define('DISABLE_WP_CRON', true);

Add to system crontab:

sh
echo '*/5 * * * * www /usr/local/bin/php /usr/local/www/wordpress/wp-cron.php > /dev/null 2>&1' >> /etc/crontab

Step 11: Automated Backups

Create a backup script:

sh
cat > /usr/local/bin/wp-backup.sh << 'BACKUPEOF' #!/bin/sh # WordPress backup script for FreeBSD BACKUP_DIR="/var/backups/wordpress" DATE=$(date +%Y%m%d_%H%M%S) DB_NAME="wordpress" DB_USER="wp_user" DB_PASS="StrongPasswordHere" WP_DIR="/usr/local/www/wordpress" mkdir -p "$BACKUP_DIR" # Database backup mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" | gzip > "$BACKUP_DIR/db_${DATE}.sql.gz" # Files backup tar czf "$BACKUP_DIR/files_${DATE}.tar.gz" -C /usr/local/www wordpress/wp-content # Remove backups older than 30 days find "$BACKUP_DIR" -name "*.gz" -mtime +30 -delete echo "Backup completed: $BACKUP_DIR/*_${DATE}*" BACKUPEOF chmod +x /usr/local/bin/wp-backup.sh # Schedule daily backup at 3 AM echo '0 3 * * * root /usr/local/bin/wp-backup.sh' >> /etc/crontab

Step 12: WordPress Updates

Automatic Core Updates

The WP_AUTO_UPDATE_CORE setting in wp-config.php (set in Step 6) handles minor version updates automatically.

Manual Updates via WP-CLI

Install WP-CLI for command-line WordPress management:

sh
fetch -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar chmod +x /usr/local/bin/wp

Use WP-CLI for updates:

sh
# Check for updates su -m www -c '/usr/local/bin/php /usr/local/bin/wp core check-update --path=/usr/local/www/wordpress' # Update WordPress core su -m www -c '/usr/local/bin/php /usr/local/bin/wp core update --path=/usr/local/www/wordpress' # Update all plugins su -m www -c '/usr/local/bin/php /usr/local/bin/wp plugin update --all --path=/usr/local/www/wordpress' # Update all themes su -m www -c '/usr/local/bin/php /usr/local/bin/wp theme update --all --path=/usr/local/www/wordpress'

FAQ

Can WordPress run inside a FreeBSD jail?

Yes, and it should for production deployments. Running WordPress in a jail isolates it from the host system and other services. Create a jail with VNET networking, install the same packages inside the jail, and follow this guide within the jail environment. ZFS cloning makes jail creation fast.

Which PHP version should I use?

Use the latest stable PHP branch available in the FreeBSD ports tree. As of this writing, PHP 8.3 is the recommended version. WordPress officially supports PHP 7.4+, but PHP 8.3 provides significant performance improvements, especially with JIT compilation.

How do I migrate an existing WordPress site to FreeBSD?

Export the database with mysqldump on the source server, copy the wp-content directory, import the database on FreeBSD with mysql < dump.sql, and update wp-config.php with the new database credentials. Update DNS to point to the FreeBSD server. Use WP-CLI's search-replace command if the domain changes: wp search-replace 'old-domain.com' 'new-domain.com'.

Why NGINX instead of Apache?

NGINX uses less memory, handles more concurrent connections, and serves static files faster than Apache. WordPress with NGINX and FastCGI caching typically handles 5-10x more concurrent users on the same hardware compared to Apache with mod_php.

How much RAM does WordPress on FreeBSD need?

A basic WordPress site runs on 1 GB RAM. For production with Redis, MariaDB, and NGINX caching, allocate at least 2 GB. For sites with 50K+ monthly visitors, 4 GB or more is recommended. The MariaDB InnoDB buffer pool is usually the largest memory consumer.

How do I troubleshoot a white screen of death?

Enable WordPress debug mode by adding define('WP_DEBUG', true); and define('WP_DEBUG_LOG', true); to wp-config.php. Check /usr/local/www/wordpress/wp-content/debug.log for PHP errors. Also check /var/log/php-fpm.log and the NGINX error log at /var/log/nginx/error.log. The most common cause is a PHP memory limit that is too low or a plugin conflict.

Can I use PostgreSQL instead of MariaDB?

WordPress does not natively support PostgreSQL. The PG4WP plugin exists but is not production-ready and breaks many plugins. Use MariaDB or MySQL for WordPress. If you need PostgreSQL for other applications on the same server, see the PostgreSQL on FreeBSD guide.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.