Nginx instead of Apache in a manual installation
Preparation
Determine which distribution you are using. The actual configuration file paths and other details depend on it.
| DEB (Debian, Ubuntu, etc.) | RPM (AlmaLinux, Rocky, CentOS, etc.) | |
|---|---|---|
| Virtual hosts | sites-available / sites-enabled by default | /etc/nginx/conf.d/*.conf by default |
Snippets (include) | mkdir -p /etc/nginx/snippets/passwork | mkdir -p /etc/nginx/snippets/passwork |
| PHP-FPM socket | /run/php/php8.3-fpm.sock | /run/php-fpm/www.sock or /var/run/php-fpm/www.sock |
| Nginx user | www-data | nginx |
| PHP-FPM pool | www-data by default | apache or nginx by default — must match the permissions on the site files |
Header snippets
Move the directives into separate configuration files in /etc/nginx/snippets/passwork/. This lets you reuse them through include.
/etc/nginx/snippets/passwork/security-headers.conf
- nginx
add_header X-Frame-Options "DENY" 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;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;
/etc/nginx/snippets/passwork/cors.conf
- nginx
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Methods "GET,HEAD,OPTIONS,POST,PUT,PATCH,DELETE" always;
add_header Access-Control-Allow-Headers "Authorization, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, X-Browser-Mode, X-Master-Key-Hash, X-CSRF-Token" always;
add_header Access-Control-Max-Age "1728000" always;
add_header Vary "Origin" always;
/etc/nginx/snippets/passwork/csp.conf
- nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests; base-uri 'none'; form-action 'self';" always;
Enabling the site and verifying it
- DEB: place the configuration file in
sites-availableand create a symlink insites-enabled. In case of a conflict, removedefault_serverfrom the default site. - RPM: if
include /etc/nginx/conf.d/*.conf;is enabled innginx.conf, a configuration file inconf.d/is enough by default.
- shell
nginx -t
systemctl reload nginx
Apache must be stopped before switching to Nginx. The main Nginx log file is /var/log/nginx/error.log
Complete example
Note:
- The
upstreamblock (uncomment the socket line for your distribution). - The HTTP→HTTPS redirect and the
serverblock for HTTPS. - Specify
server_nameand the certificate paths.
Nginx configuration for Passwork
- nginx
upstream php_passwork {
least_conn;
# DEB (Debian, Ubuntu, etc.):
# server unix:/run/php/php8.3-fpm.sock;
# RPM (AlmaLinux, Rocky, CentOS, etc.):
# server unix:/run/php-fpm/www.sock;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name passwork.example.com;
ssl_certificate /etc/ssl/passwork/fullchain.pem;
ssl_certificate_key /etc/ssl/passwork/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
root /var/www/public;
index index.html;
charset utf-8;
client_max_body_size 100M;
fastcgi_read_timeout 1800;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
location ~ /\. {
deny all;
}
location / {
include /etc/nginx/snippets/passwork/security-headers.conf;
include /etc/nginx/snippets/passwork/cors.conf;
if ($request_method = 'OPTIONS') {
return 204;
}
include /etc/nginx/snippets/passwork/csp.conf;
try_files $uri /index.html;
}
location ~ ^/api(/|$) {
include /etc/nginx/snippets/passwork/security-headers.conf;
include /etc/nginx/snippets/passwork/cors.conf;
if ($request_method = 'OPTIONS') {
return 204;
}
fastcgi_pass php_passwork;
include fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /var/www/public/index.php;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff|woff2|ttf|svg)$ {
expires 6M;
log_not_found off;
access_log off;
add_header Cache-Control "public, max-age=2592000";
}
location ~ ^/index\.php(/|$) {
try_files $uri =404;
fastcgi_pass php_passwork;
fastcgi_index index.php;
include fastcgi.conf;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
add_header Cache-Control "no-store, max-age=0" always;
}
}