Information Technology Grimoire

Version .0.0.1

IT Notes from various projects because I forget, and hopefully they help you too.

NGINX Server Blocks

NGINX Server Blocks

apt install nginx

apt install nginx

systemctl status nginx

(1:546)# systemctl status nginx.service
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2023-10-05 06:45:22 CDT; 4h 11min ago
       Docs: man:nginx(8)
    Process: 829 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
    Process: 886 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
   Main PID: 890 (nginx)
      Tasks: 3 (limit: 2309)
     Memory: 11.6M
        CPU: 101ms
     CGroup: /system.slice/nginx.service
             ├─890 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
             ├─891 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""
             └─892 "nginx: worker process" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""

Oct 05 06:45:21 vhost2023-jamesfraze systemd[1]: Starting A high performance web server and a reverse proxy server...
Oct 05 06:45:22 vhost2023-jamesfraze systemd[1]: Started A high performance web server and a reverse proxy server.

Purchase and Name Server Update

(Route 53 > Registered Domains > jamesfraze.com )[https://us-east-1.console.aws.amazon.com/route53/domains/home]

Route53 has DNSSEC and other features that digitalocean.com does not. So if I use DNSSEC, then I will need to pay the $6 a year and leave/manage with Route53. If I Do not want DNSSEC, then I’ll purchase through Route53, but then manage through digitalocean.

Delete Hosted Zone (Maybe)

I purchase from route53, but then delete the hosted zone and update name servers to digitalocean.com.

(Route 53 > Hosted Zones)[https://us-east-1.console.aws.amazon.com/route53/v2/hostedzones]

If you do not delete the hosted zone amazon charges you $6 a year to “manage” your hosted zone.

Verify A Records

If you haven’t already created proper A records and CNAME records, do that first as it could take up to 48 hours for A records to propagate. You will need this to be completely ready before you attempt to get an SSL cert from certbot.

In digitalocean: Networking > Domains > jamesfraze.com “Create New Record”

Create CNAME Records

For my particular case Here is the scenario I want:

sub.domaindesired actionTLSRecordValue
jamesfraze.comnothing, no DNS??
www.jamesfraze.com????
hosts.jamesfraze.comshow a default pageTLS
grimoire.jamesfraze.comshow my contentTLS
dig A hosts.jamesfraze.com

; <<>> DiG 9.18.12-0ubuntu0.22.04.3-Ubuntu <<>> A hosts.jamesfraze.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 36494
;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;hosts.jamesfraze.com.          IN      A

;; ANSWER SECTION:
hosts.jamesfraze.com.   0       IN      A       127.0.1.1

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Thu Oct 05 11:16:13 CDT 2023
;; MSG SIZE  rcvd: 65

Use cloudflare or other DNS servers and tools like dnschecker to verify your A record from multiple DNS servers.

(1:1473)# dig A grimoire.jamesfraze.com @1.1.1.1

; <<>> DiG 9.18.12-0ubuntu0.22.04.3-Ubuntu <<>> A grimoire.jamesfraze.com @1.1.1.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29363
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;grimoire.jamesfraze.com.       IN      A

;; ANSWER SECTION:
grimoire.jamesfraze.com. 300    IN      A       159.203.147.69

;; Query time: 156 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Oct 08 07:50:54 CDT 2023
;; MSG SIZE  rcvd: 68

Set Hostname

Only needs to be done on main/default host (not every server block)

hostnamectl set-hostname hosts.jamesfraze.com

Verify Host File

Also, only done on main server default, not every server block

cat /etc/hosts
127.0.1.1 hosts.jamesfraze.com
127.0.0.1 localhost

# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Create htpassword file

Only needs to be done once, unless you have multiple zones with the same user. Note that htpassword is not the same as htdigest. htpassword is considered less secure. htdigest uses a combination of realm and password to create a unique digest that is “hopefully” not in a rainbow table somewhere. Whereas htpassword has no salt, so is likely in a rainbow table somewhere. If your hash is revealed, it can be hacked easily.

We are creating the .htpasswd file, which will hold the hash and user name out of the web directory. An attacker would need to do directory traversal or somehow get this file that is not normally visible for it to be of any use.

Create a basic auth file as nginx cannot do htdigest:

htpasswd -c /etc/nginx/.htpasswd james

Add a Web User

If you have a user managing multiple hosts, it is still best practice to have a different webuser for each to avoid pivoting and getting data by abusing permissions.

adduser grimoire
usermod -aG sudo grimoire

This will create a folder for the user, but the folder is not web accessible.

(1:1477)# ls /home/ -alF
total 16
drwxr-xr-x  4 root        root        4096 Oct  5 12:31 ./
drwxr-xr-x 20 root        root        4096 Oct  8 07:00 ../
drwxr-x---  3 grimoire    grimoire    4096 Oct  5 12:45 grimoire/

Create Structure as User

We su as the user to properly set the file permissions. This is mostly useful in a multi-user environment. Later you will see I manually set user, group and permissions via a script. This is the best practice way though to creating files/folders as a different user to keep permissions correct:

su - grimoire
sudo mkdir -p /var/www/grimoire.jamesfraze.com/html
sudo chown -R $USER:$USER /var/www/grimoire.jamesfraze.com/html
sudo chmod -R 755 /var/www/grimoire.jamesfraze.com
vim /var/www/grimoire.jamesfraze.com/html/index.html

Main Site Config Example

The main site is different than the server blocks, which are considered virtual hosts.

(1:553)# cat /etc/nginx/nginx.conf

This is a good starting point for security, rate limiting, optimizations, etc. This is not a one sized fits all configuration though. It was customized for my 2 cpu, 2GB server that only serves static pages.

# /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    types_hash_max_size 2048;
    server_tokens off;

    # Performance settings
    tcp_nodelay on;
    keepalive_timeout 30s;

    server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    # MIME Types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # DOS Protections
    #limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s;
    #limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=300r/m;
    limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;

    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Compression Settings
    gzip on;
    gzip_vary on;
    gzip_min_length 10240;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;
    gzip_disable "MSIE [1-6]\.";

    # Directory Listing, default is off
    autoindex off;

    # Buffer Protection
    client_body_buffer_size 16K;
    client_header_buffer_size 1k;
    client_max_body_size 10m;
    large_client_header_buffers 4 8k;


    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Server Block Example (Virtual Host)

The “default” server block or virtual host is located here:

cat /etc/nginx/sites-available/default

We are going to use it as a base to create a new server block/vhost:

vim /etc/nginx/sites-available/grimoire.jamesfraze.com.conf

Here is a final example after certbot. Some things will need to be commented out as they will not work (301 redirection to https for example, ssl_session_cache, STS/HTS, OSCP stapling, listen 443, etc. You can enable them after certbot to look like this, but for now modify the default http into your new hostname.conf file and do not add all of these items yet:

server {
    if ($host = grimoire.jamesfraze.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot



    server_name grimoire.jamesfraze.com;
    listen 80;
    return 404; # managed by Certbot


}
server {
    # Basic Configuration, redirected from 80

    ssl_session_cache shared:SSL:10m;

    server_name grimoire.jamesfraze.com;
    root /var/www/grimoire.jamesfraze.com/html;
    index index.html index.htm;

    # Caching for static assets
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf)$ {
        expires 30d;
    }

    # Security Headers
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://www.googletagmanager.com 'unsafe-inline'; connect-src 'self' https://www.google-analytics.com" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin";
    add_header Permissions-Policy "accelerometer=(), autoplay=(), camera=(), display-capture=(), encrypted-media=(), fullscreen=(), geolocation=self, gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), sync-xhr=(), usb=(), xr-spatial-tracking=()";

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # OSCP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.4.4 1.0.0.1 208.67.222.222 9.9.9.9 valid=300s;
    resolver_timeout 5s;


    # Deny access to dot files, disable for .well-known checks
    location ~ /\.well-known {
        allow all;
    }
    # But block the rest of the dot files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location / {
        # Rate limiting, General
        limit_req zone=mylimit burst=50 nodelay;
        limit_conn conn_limit_per_ip 20;

        # Authentication
        auth_basic 'RESTRICTED';
        auth_basic_user_file /etc/nginx/.htpasswd;

        try_files $uri $uri/ =404;
        # Allowed HTTP methods
        if ($request_method !~ ^(GET|HEAD|POST)$ ) {
            return 405;
        }
    }

    # Logging
    access_log /var/log/nginx/grimoire.jamesfraze.com_access.log;
    error_log /var/log/nginx/grimoire.jamesfraze.com_error.log;

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;


    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/grimoire.jamesfraze.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/grimoire.jamesfraze.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

Enable a Site

Apacher uses a2ensite which basically creates a symlink. Nginx does not have this utility. Intead you create the symlink manually.

sudo ln -s /etc/nginx/sites-available/example.conf /etc/nginx/example

Disable a Site

If you do need to disable a site, this is the way:

sudo rm /etc/nginx/sites-enabled/example.conf

Basic Files

touch the basic files

(1:556)# pwd
/var/www/grimoire.jamesfraze.com

(1:556)# ls html/
total 12
drwxr-xr-x 2 grimoire www-data 4096 Sep 25 22:50 ./
drwxr-xr-x 3 root        root     4096 Sep 25 20:21 ../
-rw-r--r-- 1 grimoire www-data    0 Sep 25 22:48 404.html
-rw-r--r-- 1 grimoire www-data    0 Sep 25 22:48 50x.html
-rw-r--r-- 1 grimoire www-data    0 Sep 25 22:50 favicon.ico
-rw-rw-r-- 1 grimoire www-data  123 Sep 25 22:47 index.html

Test Configuration

nginx -t

Restart NGINX

systemctl restart nginx
systemctl status nginx

Error/Access Logs

The main site access/error logs are separate from each vhost, if you configured the way I recommended:

tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log

Each vhost has it’s own logs:

tail -f /var/log/nginx/grimoire.jamesfraze.com_error.log
tail -f /var/log/nginx/grimoire.jamesfraze.com_access.log

Verify FW Access

If using UFW and host based firewall:

(1:562)# ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 22/tcp                     ALLOW IN    Anywhere                   # SSH for IPv4 only
[ 2] 80/tcp                     ALLOW IN    Anywhere                   # HTTP for IPv4 only
[ 3] 443/tcp                    ALLOW IN    Anywhere                   # HTTPS for IPv4 only
[ 4] 443/udp                    ALLOW IN    Anywhere                   # QUIC/HTTP2 IPv4 only

If using digitalocean, Droplets > the droplet > Networking > Firewall Section (review the WAF)

Verify Browser Access

From a remote host, not the server, verify it works

curl http://grimoire.jamesfraze.com/

Next Steps

If it works, it’s time to install the cert using certbot.

certbot ssl nginx