Difference between revisions of "OpenEMR with nginx and php-fpm"

From OpenEMR Project Wiki
 
(40 intermediate revisions by the same user not shown)
Line 11: Line 11:


This is my setup (FEMP)
This is my setup (FEMP)
::openemr-5.0.1_3
::FreeBSD 12.1 (includes openSSL 1.1.1)
::FreeBSD 11.1
::nginx -1.14.0
::nginx -1.14.0
::Mysql-5.7
::Mysql-5.7
::PHP-7.2 (includes the php-fpm server)
::PHP-7.4 (includes the php-fpm server)
::openemr-5.0.2_2


Important information regarding Nginx search instructions, placement and whitespaces are important:
Important information regarding Nginx search instructions, placement and whitespaces are important:


  ^~  stop when found
  = exact match
  ~  case sensitive
  ^~  stop when found (preferential prefix)
  ~* case insensitive
  ~  case sensitive (regex match)
  ~* case insensitive (regex match)
      prefix match (no modifier)
 
From the [https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/#directives Nginx Administrator Guide] :
 
"To make the configuration easier to maintain, we recommend that you split it into a set of feature‑specific files stored in the [/usr/local/etc/nginx] directory and use the include directive in the main nginx.conf file to reference the contents of the feature‑specific files."


==Configuration files==  
==Configuration files==  
Line 32: Line 38:
     worker_connections  1024;  
     worker_connections  1024;  
}
}
# start the http block
# start the http block
http {
http {
   include      mime.types;
   include      mime.types;
  #enable server-wide security/ssl
  include      http-ssl.conf
   default_type  application/octet-stream;
   default_type  application/octet-stream;


Line 44: Line 55:
   # don't send the nginx version number in error pages and Server header
   # don't send the nginx version number in error pages and Server header
   server_tokens off;
   server_tokens off;
  # config to don't allow the browser to render the page inside an frame or iframe
  # and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
  # if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
  # https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
  add_header X-Frame-Options SAMEORIGIN;
  # when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
  # to disable content-type sniffing on some browsers.
  # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
  add_header X-Content-Type-Options nosniff;
  # This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
  # It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
  # this particular website if it was disabled by the user.
  # https://www.owasp.org/index.php/List_of_useful_HTTP_headers
  add_header X-XSS-Protection "1; mode=block";
  # with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
  # you can tell the browser that it can only download content from the domains you explicitly allow
  # http://www.html5rocks.com/en/tutorials/security/content-security-policy/
  # https://www.owasp.org/index.php/Content_Security_Policy
  # I need to find out if we can increase security by disabling 'unsafe-inline' 'unsafe-eval' for openemr
  # more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
  #add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'";


   sendfile        on;
   sendfile        on;
Line 79: Line 65:
   gzip  off;
   gzip  off;
   upstream php {  
   upstream php {  
       server unix:/var/run/php-fpm.sock;  
       # this is the php socket or TCP/IP port, uncomment whichever applies to your setup.
      # it should match value of "listen" directive in php-fpm pool
server unix:/tmp/php-fpm.sock;
              #server 127.0.0.1:9000;
   }
   }
   index  index.html index.htm index.php;
   index  index.html index.htm index.php;
Line 86: Line 75:
   #  redirect all http traffic to https
   #  redirect all http traffic to https
   server {
   server {
       listen 80 default_server;
       listen     80 default_server http2;
       listen [::]:80 default_server;
       listen [::]:80 default_server http2;
       server_name  example.net www.example.net;
       server_name  example.net www.example.net;
        
        
      # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
      # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
      return 301 https://$host$request_uri;
      return 301 https://example.net$request_uri;
  }
 
  #  www.example.net https host
  #  This is an ssl www openemr site which redirects to no-www
  server {
listen      443 ssl http2;
        listen [::]:443 ssl http2;   
server_name  www.example.net;
        #send the request to no-www
        return 301 https://example.net$request_uri;
   }
   }


   #  openemr https host
   #  example.net https host
   #  This is an ssl only openemr site
   #  This is an ssl only openemr site
   server {
   server {
       listen 443 default_server ssl http2;
       listen     443 default_server ssl http2;
       listen  [::]:443 default_server ssl http2;
       listen  [::]:443 default_server ssl http2;
       server_name  example.net www.example.net;
       server_name  example.net www.example.net;
       root /usr/local/nginx/www;
       root /usr/local/nginx/www;   
 
      ## redirect www to nowww
      if ($host = 'www.example.net' ) {
rewrite  ^/(.*)$  https://example.net/$1  permanent;
      }    


       access_log /var/log/*/example.net_access_log main;
       access_log /var/log/*/example.net_access_log main;
Line 123: Line 117:
       # deny access to writable files/directories
       # deny access to writable files/directories
       location ~* ^/sites/*/(documents|edi|era) {  
       location ~* ^/sites/*/(documents|edi|era) {  
deny all;  
deny all;
                return 404;
      }
 
      # deny access to certain directories
      location ~* ^/(contrib|tests) {
deny all;
                return 404;
       }
       }
       # Pick one of the following two blockc, but not both:
       # Uncomment one of the following two blocks, but not both:
       # protect special files from outside openemer login, and restrict them to superAdmins only
       # protect special files from outside openemer login, and restrict them to superAdmins only
       location ~* ^/(admin|setup|acl_setup|acl_upgrade|sl_convert|sql_upgrade|gacl/setup|ippf_upgrade|sql_patch)\.php {
       #location ~* ^/(admin|setup|acl_setup|acl_upgrade|sl_convert|sql_upgrade|gacl/setup|ippf_upgrade|sql_patch)\.php {
auth_basic "Restricted Access";  
# auth_basic "Restricted Access";  
auth_basic_user_file /path/to/.htpasswd;
# auth_basic_user_file /path/to/.htpasswd;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  
fastcgi_pass php;  
# fastcgi_pass php;  
include fastcgi_params;  
# include fastcgi_params;  
       }
       #}
      # If you pick this block, be sure to comment the previous one
       # Alternatively all access to these files can be denied
       # Alternatively all access to these files can be denied
       #location ~* ^/(admin|setup|acl_setup|acl_upgrade|sl_convert|sql_upgrade|gacl/setup|ippf_upgrade|sql_patch)\.php {  
       #location ~* ^/(admin|setup|acl_setup|acl_upgrade|sl_convert|sql_upgrade|gacl/setup|ippf_upgrade|sql_patch)\.php {  
Line 153: Line 154:
root  /usr/local/www/nginx-dist;  
root  /usr/local/www/nginx-dist;  
       }  
       }  
    # rewrite location statement for Zend modules at /interface/modules/zend_modules/public/Installer
    # it is meant to replace Apache's .htaccess file rewrite rule: RewriteRule ^.*$ - [NC,L], RewriteRule ^.*$ index.php [NC,L]
      
      
       # pass the PHP scripts to the FastCGI server listening on unix socket, in this case php-fpm
       # pass the PHP scripts to the FastCGI server listening on unix socket, in this case php-fpm
Line 158: Line 162:
try_files $uri =404;
try_files $uri =404;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
             fastcgi_pass php;
             fastcgi_pass php;
                 include fastcgi_params;  
                 include fastcgi_params;  
Line 216: Line 219:
# Stop deep linking or hot linking
# Stop deep linking or hot linking
location /images/ {
location /images/ {
   valid_referers none blocked www.example.com example.com;
   valid_referers none blocked www.example.net example.net;
   if ($invalid_referer) {
   if ($invalid_referer) {
       return  403;
       return  403;
Line 240: Line 243:
     fastcgi_read_timeout 120;
     fastcgi_read_timeout 120;
}
}
</pre>
===/usr/local/etc/nginx/http-ssl.conf===
Use this file to enable server-wide security. This is intended to be used in the http { } block
<pre>
# config to don't allow the browser to render the page inside an frame or iframe
# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;
# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-Content-Type-Options nosniff;
# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";
# with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
# you can tell the browser that it can only download content from the domains you explicitly allow
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# https://www.owasp.org/index.php/Content_Security_Policy Also check this site out for more information: https://content-security-policy.com/
# more information at: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
# This will break the openemr site. This is for developers who are familiar with CSP only. You may try to get a policy from this site: https://www.cspisawesome.com/ Use this at your own risk!
#add_header Content-Security-Policy "default-src 'self'; script-src 'self'; img-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://themes.googleusercontent.com; child-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; plugin-types 'self'; report-uri 'self'";
# Notify the client that this domain, and its subdomains, must only be accessed using HTTPS (SSL or TLS),
# and have it cache this information for the max-age period: typically 31,536,000 seconds, equal to about 1 year
# For more information: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
# Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS and have submitted their names to https://hstspreload.appspot.com/.
# This domain list is distributed and hardcoded into major web browsers. Clients that access web domains in this list automatically use HTTPS and refuse to access the site using HTTP.
# Be aware that once you set the STS header or submit your domains to the HSTS preload list, it is impossible to remove it.
# It’s a one‑way decision to make your domains available over HTTPS.
# "always" at end of statement requires nginx 1.7 or later
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months), choose only one
#add_header Strict-Transport-Security max-age=15768000;
#add_header Strict-Transport-Security "max-age=15724800; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Referrer policy
add_header Referrer-Policy "no-referrer";
# Enable SSL session caching to enable session resumption for improved performance
# http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
# http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
</pre>
</pre>


Line 249: Line 305:
# For more information: https://wiki.mozilla.org/Security/Server_Side_TLS and  
# For more information: https://wiki.mozilla.org/Security/Server_Side_TLS and  
# https://gist.github.com/plentz/6737338
# https://gist.github.com/plentz/6737338
# enable session resumption to improve https performance
# http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;


# disable SSLv3, (enabled by default since nginx 0.8.19)  
# disable SSLv3, (enabled by default since nginx 0.8.19)  
Line 270: Line 320:


# Notify the client that this domain, and its subdomains, must only be accessed using HTTPS (SSL or TLS),  
# Notify the client that this domain, and its subdomains, must only be accessed using HTTPS (SSL or TLS),  
# and have it cache this information for the max-age period: typically 31,536,000 seconds, equal to about 1 year
# and have it cache this information for the max-age period: typically 31,536,000 seconds, equal to about 1 year
# For more information: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
# For more information: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
# Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS and have submitted their names to https://hstspreload.appspot.com/.  
# Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS and have submitted their names to https://hstspreload.appspot.com/.  
# This domain list is distributed and hardcoded into major web browsers. Clients that access web domains in this list automatically use HTTPS and refuse to access the site using HTTP.
# This domain list is distributed and hardcoded into major web browsers. Clients that access web domains in this list automatically use HTTPS and refuse to access the site using HTTP.
# Be aware that once you set the STS header or submit your domains to the HSTS preload list, it is impossible to remove it.  
# Be aware that once you set the STS header or submit your domains to the HSTS preload list, it is impossible to remove it.  
# It’s a one‑way decision to make your domains available over HTTPS.
# It’s a one‑way decision to make your domains available over HTTPS only.
# "always" at end of statement requires nginx 1.7 or later
# "always" at end of statement requires nginx 1.7 or later
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Line 298: Line 348:
# uncomment this line if localhost is running BIND
# uncomment this line if localhost is running BIND
#resolver 127.0.0.1 [::1]:5353 valid=300s;
#resolver 127.0.0.1 [::1]:5353 valid=300s;
# google resolvers
# use https://blog.cloudflare.com/announcing-1111 Cloudfare+Apnic labs, It is free and secure
resolver 8.8.8.8 8.8.4.4 [2001:4860:4860::8888] [2001:4860:4860::8844] valid=300s;
resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s;
resolver_timeout 5s;
</pre>
</pre>


Line 313: Line 362:
::Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS. To submit your domain got to, https://hstspreload.appspot.com/ This will cause your domain to only be accessible via https. Be careful, this is not reversible. Your site will no longer be accessible via http.
::Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS. To submit your domain got to, https://hstspreload.appspot.com/ This will cause your domain to only be accessible via https. Be careful, this is not reversible. Your site will no longer be accessible via http.
::https://caniuse.com/
::https://caniuse.com/
::Check your [https://securityheaders.com/ Security Headers]
::[https://observatory.mozilla.org/ Mozilla's Observatory]


==References==
==References==


::Nginx official site, http://nginx.org/
::[http://nginx.org/ Nginx] official site
::FreeBSD official site, https://www.freebsd.org/
::[https://nginx.org/en/docs/http/configuring_https_servers.html Setting up HTTPS in Nginx]
::FreeBSD applications/ports, https://www.freshports.org/
::[https://www.freebsd.org/ FreeBSD] official site
::PHP-FPM official site, https://php-fpm.org/
::FreeBSD applications/ports, [https://www.freshports.org/ Freshports]
::Tutorial for installing free [let's encrypt] certificates in FreeBSD with Nginx https://www.tecmint.com/install-lets-encrypt-ssl-for-nginx-on-freebsd/comment-page-1/#comment-1003587
::[https://php-fpm.org/ PHP-FPM] official site
::[https://www.tecmint.com/install-lets-encrypt-ssl-for-nginx-on-freebsd/comment-page-1/#comment-1003587/ Tutorial] for installing free [https://letsencrypt.org/ Let's Encrypt] certificates in FreeBSD with Nginx.
::Fjordvald, Martin Bjerretoft, and Clement Nedelcu. ''Nginx HTTP Server Harness the Power of Nginx to Make the Most of Your Infrastructure and Serve Pages Faster than Ever Before''. Packt Publishing, 2018.
::Fjordvald, Martin Bjerretoft, and Clement Nedelcu. ''Nginx HTTP Server Harness the Power of Nginx to Make the Most of Your Infrastructure and Serve Pages Faster than Ever Before''. Packt Publishing, 2018.


===Security===
===Security===
::SSL/TLS:
::SSL/TLS:
:::https://wiki.mozilla.org/Security/Server_Side_TLS
:::Mozilla's [https://wiki.mozilla.org/Security/Server_Side_TLS Security/Server Side TLS]
:::https://www.ssllabs.com/projects/best-practices/index.html
:::SSL Labs' [https://www.ssllabs.com/projects/best-practices/index.html SSL and TLS Deployment Best Practices]
::SSL and Nginx:
::SSL and Nginx:
:::https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
:::[https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html Strong SSL Security on Nginx]
:::ssl.conf file, https://gist.github.com/plentz/6737338
:::Useful ssl configuration information [https://gist.github.com/plentz/6737338 ssl.conf file]
::avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
:::I found this article very useful [https://www.mare-system.de/guide-to-nginx-ssl-spdy-hsts/ Guide to nginx ssl spdy hsts]
::Avoid clickjacking [http://en.wikipedia.org/wiki/Clickjacking Wikipedia's Clickjacking page]
::Content Security Policy, CSP:
::Content Security Policy, CSP:
:::https://www.owasp.org/index.php/Content_Security_Policy
:::Open Web Application Security Project [https://www.owasp.org/index.php/Content_Security_Policy page on CSP]
:::http://www.html5rocks.com/en/tutorials/security/content-security-policy/
:::html5rocks [http://www.html5rocks.com/en/tutorials/security/content-security-policy/ CSP tutorial]
::SSL session reuse, http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
::[http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html SSL session reuse]
::Forward secrecy, http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
::[http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html Configuring Apache Nginx and Openssl for Forward secrecy]
::X Frames, https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
::[https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options X Frames Options]
::Headers, https://www.owasp.org/index.php/List_of_useful_HTTP_headers
::Open Web Application Security Project List of [https://www.owasp.org/index.php/List_of_useful_HTTP_headers useful HTTP headers]
::HSTS and Nginx:
::[https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/ HSTS and Nginx]
:::https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
::OCSP Stapling:
::OCSP Stapling and Nginx:
:::[https://raymii.org/s/tutorials/OCSP_Stapling_on_nginx.html OCSP Stapling on Nginx]
:::https://raymii.org/s/tutorials/OCSP_Stapling_on_nginx.html
:::[http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/ OCSP Stapling in Firefox]
:::http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
::Exploits:
::Exploits:
:::http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
:::[http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html Is Beast still a threat?]
[https://www.internetsociety.org/deploy360/dnssec/tools/ DNS tools]

Latest revision as of 21:22, 23 August 2021

Introduction

The LAMP stack (Linux + Apache + MySQL + PHP) is very popular for powering OpenEMR. However is it possible to use Nginx?. OpenEMR may support Nginx, in fact, some OpenEMR sites are powered by Nginx as the reverse proxy with Apache. At least that is my understanding. A while back, after an update, my OpenEMR, FreeBSD/Apache/Mysql/PHP stack broke down. That is when I started looking for other solutions and came across Nginx and I liked it.

There are multiple ways to implement Nginx. However, this guide implements Nginx as the primary server instead of Apache. Please note that Apache and Nginx are not interchangeable. Hence this guide. For example, there is no directory-level configuration (.htaccess or IIS's Web.config files) in Nginx. All configuration must be done at the server level, and the server must be reloaded with each change.

Initially, this guide assumes you already have a working SSL enabled Nginx server with PHP7.2 and php-fpm is enabled and running. Later on, I may add sections on how to install the different components in a FreeBSD system, which is what I know. Hopefully, you already have a basic understanding of how to work with Nginx and how to debug it.

Please note that this document is a work in progress. There may be bugs in this setup. Use it at your own risk. Please post your questions at the forum, https://community.open-emr.org/ I will be notifed if you include @gutiersa in your topic, your questions will help others. Comments are also welcome, please be kind.

We set up OpenEMR with SSL right from the start. In order for OpenEMR to work with Nginx, you have to configure the backend php-cgi. The Nginx default installation comes with three options for implementation of the CGI module: 'fastcgi', 'scgi' and 'uwsgi'. The CGI module connects Nginx and the php-fpm (PHP Fast Program Manager). We use the FastCGI module. The "fastcgi_params" file can already be found in the default Nginx configuration directory, which in FreeBSD is located at /usr/local/etc/nginx/

This is my setup (FEMP)

FreeBSD 12.1 (includes openSSL 1.1.1)
nginx -1.14.0
Mysql-5.7
PHP-7.4 (includes the php-fpm server)
openemr-5.0.2_2

Important information regarding Nginx search instructions, placement and whitespaces are important:

  = exact match
^~  stop when found (preferential prefix)
~  case sensitive (regex match)
~* case insensitive (regex match)
     prefix match (no modifier)

From the Nginx Administrator Guide :

"To make the configuration easier to maintain, we recommend that you split it into a set of feature‑specific files stored in the [/usr/local/etc/nginx] directory and use the include directive in the main nginx.conf file to reference the contents of the feature‑specific files."

Configuration files

/usr/local/etc/nginx/nginx.conf:

worker_processes  auto;
error_log  /var/log/nginx/error.log;
events { 
    worker_connections  1024; 
}

# start the http block
http {
  include       mime.types;

  #enable server-wide security/ssl
  include       http-ssl.conf 

  default_type  application/octet-stream;

  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
  access_log  /var/log/access.log  main;

  # don't send the nginx version number in error pages and Server header
  server_tokens off;

  sendfile        on;
  #tcp_nopush     on;

  #keepalive_timeout  0;
  server_names_hash_bucket_size 128;
  keepalive_timeout  65;

  gzip  off;
  upstream php { 
      # this is the php socket or TCP/IP port, uncomment whichever applies to your setup.
      # it should match value of "listen" directive in php-fpm pool
		server unix:/tmp/php-fpm.sock;
              #server 127.0.0.1:9000;
  }
  index  index.html index.htm index.php;

  #  http host
  #  redirect all http traffic to https
  server {
      listen      80 default_server http2;
      listen [::]:80 default_server http2;
      server_name  example.net www.example.net;
      
       # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
       return 301 https://example.net$request_uri;
  }

  #   www.example.net https host
  #   This is an ssl www openemr site which redirects to no-www
  server {
	listen      443 ssl http2;
        listen [::]:443 ssl http2;    
	server_name  www.example.net;
        #send the request to no-www
        return 301 https://example.net$request_uri;
  }

  #   example.net https host
  #   This is an ssl only openemr site
  server {
      listen 	     443 default_server ssl http2;
      listen  [::]:443 default_server ssl http2;
      server_name  example.net www.example.net;
      root /usr/local/nginx/www;   

      access_log /var/log/*/example.net_access_log main;
      error_log    /var/log/*/example.net_error_log notice;

      # openemr specific SSL settings
      # to customize your configuration: https://mozilla.github.io/server-side-tls/ssl-config-generator/
      # configuration can be placed here, or in a separate file named openemr-ssl.conf
      include openemr-ssl.conf;  

      ssl_certificate      /path/to/ssl.cert;
      ssl_certificate_key    /path/to/ssl.key;

      # restrict/protect certain files
      include globals.conf;   

      # deny access to writable files/directories
      location ~* ^/sites/*/(documents|edi|era) { 
		deny all;
                return 404; 
      }

      # deny access to certain directories
      location ~* ^/(contrib|tests) { 
		deny all;
                return 404;
      }
	
      # Uncomment one of the following two blocks, but not both:
      # protect special files from outside openemer login, and restrict them to superAdmins only
      #location ~* ^/(admin|setup|acl_setup|acl_upgrade|sl_convert|sql_upgrade|gacl/setup|ippf_upgrade|sql_patch)\.php {
	#	auth_basic 				"Restricted Access"; 
	#	auth_basic_user_file 	/path/to/.htpasswd;
	#	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 
	#	fastcgi_pass php; 
	#	include fastcgi_params; 
      #}
      # If you pick this block, be sure to comment the previous one
      # Alternatively all access to these files can be denied
      #location ~* ^/(admin|setup|acl_setup|acl_upgrade|sl_convert|sql_upgrade|gacl/setup|ippf_upgrade|sql_patch)\.php { 
      #	deny all; 
      #	return 404; 
      #}

      location / {
		# try as file ($uri), as directory ($uri/) if not found, send to index file
		# no php is touched for static content
        	try_files $uri $uri/ /index.php;
      }	
	
      # redirect server error pages to the static page /50x.html
      error_page   500 502 503 504  /50x.html;
      location = /50x.html { 
		root   /usr/local/www/nginx-dist; 
      } 

    # rewrite location statement for Zend modules at /interface/modules/zend_modules/public/Installer
    # it is meant to replace Apache's .htaccess file rewrite rule: RewriteRule ^.*$ - [NC,L], RewriteRule ^.*$ index.php [NC,L] 
    
      # pass the PHP scripts to the FastCGI server listening on unix socket, in this case php-fpm
      location ~* \.php$ {
		try_files $uri =404;
        	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    	        fastcgi_pass php;
                include fastcgi_params; 
      }
      # dynamic stuff goes to php-proxy
      include php-proxy.conf;
    }
} # end http block

/usr/local/etc/nginx/globals.conf file:

# globals.conf configuration file.
# Designed to be included in any server {} block
# If this server only hosts openemr, this file can be merged with openemr.conf

# Stops the annoying error messages in the logs. robots are not allowed
location = /favicon.ico { 
      log_not_found off; 
      access_log off; 
}

location = /robots.txt  { 
      log_not_found off; 
      access_log off; 
}

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
      deny all; 
}

# protect or deny access to important server information and testing files
# alternatively, you can deny access to all files using {deny all; return 404;} or remove them
location ~* /(info|test)\.php$ {
	auth_basic "Restricted Access";
	auth_basic_user_file /path/to/.htpasswd;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 
	fastcgi_pass php; 
	include fastcgi_params; 
}

# Not sure if openemr needs this. it comes from wordpress
location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { 
	access_log off; 
	log_not_found off; 
	expires max;
}

## Deny certain Referers
if ( $http_referer ~* (babes|forsale|girl|jewelry|love|nudit|organic|poker|porn|sex|teen) ) {
         #return 404;
         return 403;
}
     
# Stop deep linking or hot linking
location /images/ {
  valid_referers none blocked www.example.net example.net;
  if ($invalid_referer) {
       return   403;
   }
}

/usr/local/etc/nginx/php-proxy.conf:

# php proxy block
# should have "cgi.fix_pathinfo = 0;" in php.ini
#
location ~ \.php$ {
    # php-fpm must be in same machine. Otherwise there is risk of hacking
    try_files $uri =404;
    fastcgi_index  index.php;
    fastcgi_pass unix:/var/run/php-fpm.sock;
    fastcgi_buffer_size 16k;
    fastcgi_buffers 4 16k;
    fastcgi_param SCRIPT_FILENAME $request_filename;
    include fastcgi_params;
    fastcgi_intercept_errors on;
    fastcgi_read_timeout 120;
}

/usr/local/etc/nginx/http-ssl.conf

Use this file to enable server-wide security. This is intended to be used in the http { } block

# config to don't allow the browser to render the page inside an frame or iframe
# and avoid clickjacking http://en.wikipedia.org/wiki/Clickjacking
# if you need to allow [i]frames, you can use SAMEORIGIN or even set an uri with ALLOW-FROM uri
# https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options
add_header X-Frame-Options SAMEORIGIN;

# when serving user-supplied content, include a X-Content-Type-Options: nosniff header along with the Content-Type: header,
# to disable content-type sniffing on some browsers.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-Content-Type-Options nosniff;

# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for 
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";

# with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
# you can tell the browser that it can only download content from the domains you explicitly allow
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# https://www.owasp.org/index.php/Content_Security_Policy Also check this site out for more information: https://content-security-policy.com/
# more information at: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
# This will break the openemr site. This is for developers who are familiar with CSP only. You may try to get a policy from this site: https://www.cspisawesome.com/ Use this at your own risk!
#add_header Content-Security-Policy "default-src 'self'; script-src 'self'; img-src 'self'; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://themes.googleusercontent.com; child-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; plugin-types 'self'; report-uri 'self'";

# Notify the client that this domain, and its subdomains, must only be accessed using HTTPS (SSL or TLS), 
# and have it cache this information for the max-age period: typically 31,536,000 seconds, equal to about 1 year
# For more information: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
# Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS and have submitted their names to https://hstspreload.appspot.com/. 
# This domain list is distributed and hardcoded into major web browsers. Clients that access web domains in this list automatically use HTTPS and refuse to access the site using HTTP.
# Be aware that once you set the STS header or submit your domains to the HSTS preload list, it is impossible to remove it. 
# It’s a one‑way decision to make your domains available over HTTPS.
# "always" at end of statement requires nginx 1.7 or later
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months), choose only one
#add_header Strict-Transport-Security max-age=15768000;
#add_header Strict-Transport-Security "max-age=15724800; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# Referrer policy
add_header Referrer-Policy "no-referrer";

# Enable SSL session caching to enable session resumption for improved performance
# http://vincent.bernat.im/en/blog/2011-ssl-session-reuse-rfc5077.html
# http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

/usr/local/etc/nginx/openemr-ssl.conf:

Please note that this configuration is intended to provide tight protection to your openemr site. This will not work with older version browsers. For best results, start here: https://mozilla.github.io/server-side-tls/ssl-config-generator/ to generate your own custom configuration.

# openemr gets its own SSL configuration. It should be stricter than a regular ssl.conf
# SSL, go here to test your server: https://www.ssllabs.com/ssltest/
# For more information: https://wiki.mozilla.org/Security/Server_Side_TLS and 
# https://gist.github.com/plentz/6737338

# disable SSLv3, (enabled by default since nginx 0.8.19) 
#  also disable TLSv1 TLSv1.1, however this requires more modern browsers.
ssl_protocols TLSv1.2;
# ciphers chosen for forward secrecy and compatibility
# http://blog.ivanristic.com/2013/08/configuring-apache-nginx-and-openssl-for-forward-secrecy.html
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
# enables server-side protection from BEAST attacks
# http://blog.ivanristic.com/2013/09/is-beast-still-a-threat.html
ssl_prefer_server_ciphers on;

# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;

# Notify the client that this domain, and its subdomains, must only be accessed using HTTPS (SSL or TLS), 
# and have it cache this information for the max-age period: typically 31,536,000 seconds, equal to about 1 year
# For more information: https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
# Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS and have submitted their names to https://hstspreload.appspot.com/. 
# This domain list is distributed and hardcoded into major web browsers. Clients that access web domains in this list automatically use HTTPS and refuse to access the site using HTTP.
# Be aware that once you set the STS header or submit your domains to the HSTS preload list, it is impossible to remove it. 
# It’s a one‑way decision to make your domains available over HTTPS only.
# "always" at end of statement requires nginx 1.7 or later
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html for more information
# to generate your dhparam.pem file, run in the terminal: openssl dhparam -out /path/to/dhparam.pem 2048
ssl_dhparam /path/to/dhparam.pem;

# enable ocsp stapling (mechanism by which a site can convey certificate revocation information to visitors in a privacy-preserving, scalable manner)
# http://blog.mozilla.org/security/2013/07/29/ocsp-stapling-in-firefox/
#  https://raymii.org/s/tutorials/OCSP_Stapling_on_nginx.html
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;

## verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /path/to/root_CA_cert_plus_intermediates;

# RESOLVERS
#resolver 	$DNS-IP-1 	$DNS-IP-2 	valid=300s;
# uncomment this line if localhost is running BIND
#resolver 	127.0.0.1 	[::1]:5353 	valid=300s;
# use https://blog.cloudflare.com/announcing-1111 Cloudfare+Apnic labs, It is free and secure
resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001] valid=300s;

Tools

Generate a conf file for multiple webservers including Nginx and Apache at: https://mozilla.github.io/server-side-tls/ssl-config-generator/
Check your ssl configuration: https://www.ssllabs.com/ssltest/
Geek Flare tools: https://tools.geekflare.com/tools
Cloudpiercer scanning tool to check if your IP is exposed: https://cloudpiercer.org
HPKP or HTTP Public Key Pinning: https://developer.mozilla.org/en-US/docs/Web/HTTP/Public_Key_Pinning
Content Security Policy (CSP): Find out which browsers support it. http://caniuse.com/#feat=contentsecuritypolicy
Google maintains a “HSTS preload list” of web domains and subdomains that use HSTS. To submit your domain got to, https://hstspreload.appspot.com/ This will cause your domain to only be accessible via https. Be careful, this is not reversible. Your site will no longer be accessible via http.
https://caniuse.com/
Check your Security Headers
Mozilla's Observatory

References

Nginx official site
Setting up HTTPS in Nginx
FreeBSD official site
FreeBSD applications/ports, Freshports
PHP-FPM official site
Tutorial for installing free Let's Encrypt certificates in FreeBSD with Nginx.
Fjordvald, Martin Bjerretoft, and Clement Nedelcu. Nginx HTTP Server Harness the Power of Nginx to Make the Most of Your Infrastructure and Serve Pages Faster than Ever Before. Packt Publishing, 2018.

Security

SSL/TLS:
Mozilla's Security/Server Side TLS
SSL Labs' SSL and TLS Deployment Best Practices
SSL and Nginx:
Strong SSL Security on Nginx
Useful ssl configuration information ssl.conf file
I found this article very useful Guide to nginx ssl spdy hsts
Avoid clickjacking Wikipedia's Clickjacking page
Content Security Policy, CSP:
Open Web Application Security Project page on CSP
html5rocks CSP tutorial
SSL session reuse
Configuring Apache Nginx and Openssl for Forward secrecy
X Frames Options
Open Web Application Security Project List of useful HTTP headers
HSTS and Nginx
OCSP Stapling:
OCSP Stapling on Nginx
OCSP Stapling in Firefox
Exploits:
Is Beast still a threat?

DNS tools