Skip to content

Tesselay/hardened-nginx-webserver

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hardened Nginx Webserver

Commits Last Commit Issues Unlicense License

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Debugging
  6. Contributing
  7. License
  8. Acknowledgments

About The Project

This is an Ansible playbook to set up, harden and configure an nginx webserver on Debian.

Features:

  • Cloudinit config included
  • Public Key only SSH
  • Basic Linux hardening included
  • Unprivileged & Sandboxed nginx server/user
  • Auto SSL Certificates

Built With

Ansible Badge NGINX Badge

Getting Started

Prerequisites

This is built with Debian 12 but can be modified for different distros.

Installation

Generate SSH key on your client

ssh-keygen

(Optional) If not using cloudinit, provide ssh key to server.

ssh-copy-id -i <public-key-location> root@<host>

Install Collections

ansible-galaxy collection install -r requirements.yml -p ./collections

Configuration

Configure Cloudinit

Add hostname, deployment user name, ssh key and timezone to ./example.cloudinit-debian.yml and rename file to cloudinit-debian.yml.

Configure Hosts

Change both IPs in ansible/inventory/example.hosts.ini and rename file to hosts.ini.

Configure Group Variables

Customize ssh port and change ansible user to deployment user in ansible/group_vars/example.all.yml and rename file to all.yml.

Customize ssh port, domains, webroot and add admin mail and ssh private key location to ansible/group_vars/webserver/example.settings.yml and rename file.

Optionally change ACME authority inside ansible/roles/server/vars/main.yml if you want to use a different one than Let's Encrypt.

Create vault

You will need a hash of your password, for example via mkpasswd. For rounds I choose a value above 100k but this barely matters (a strong password does though).

mkpasswd -m sha512crypt --rounds=<VALUE>

Now see the example vault for the template. Add the name of your deployment user (same as in cloudinit), the hash of the password you chose and the pubkey (if you're not using cloudinit). Add the password to the nginx user and set Ansible's privilege escalation password to the deployment user's unhashed password.

ansible-vault create ansible/group_vars/webserver/vault.yml

Usage

Setup SSH agent on client

Only really recommended when using a passphrase-protected ssh key so that you don't have to reenter it every time you run a play.

eval "$(ssh-agent -s)"
ssh-add <private-key-location>

Run playbooks

ansible-playbook server_baseline.yml -i ./inventory/hosts.ini --ask-vault-pass
ansible-playbook server_config.yml -i ./inventory/hosts.ini --ask-vault-pass

Roadmap

  • Add website role
  • Define recurring jobs to run tasks, mainly cert check/renewal and site update
  • Add lab/test environment for dev
  • Add maintenance user/admin, instead of giving deployment user all rights

Debugging

Certificates

Staging Environment

To debug certificate issues, change the acme authority to the staging environment of Let's Encrypt (or your CA of choice). This prevents running into rate limits.

Change the acme authority variable to acme_authority: https://acme-staging-v02.api.letsencrypt.org/directory. I added a vars line to the include_role task.

Certificate Expiration Date

openssl x509 -in /etc/nginx/ssl/webserver/fullchain.pem -noout -enddate

What Certificate nginx Serves

echo | openssl s_client -connect <DOMAINNAME>:443 -servername <DOMAINNAME> 2>/dev/null \
     | openssl x509 -noout -issuer -subject -dates

HTTP Connection

Check ALPN Negotiation

openssl s_client -connect <DOMAINNAME>:443 -servername <DOMAINNAME> -alpn h2 -brief </dev/null

Curl HTTP Connection

# HTTP/2
curl -vkI --http2 https://<DOMAINNAME>/

# Force HTTP/1.1
curl -vkI --http1.1 https://<DOMAINNAME>/

If HTTP/1.1 works but HTTP/2 fails, it’s almost certainly the listener (listen 443 ssl http2; missing/duplicated), a buggy module combo (gzip/brotli/header munging), or a Content‑Length mismatch on compressed output.

If both fail the same way, it may be root/try_files/permissions.

Hard test HTTP Version

To determine whether a problem is caused by HTTP/2, temporarily disable it by removing the http2 parameter from your TLS listener:

listen 443 ssl;
sudo nginx -t && sudo systemctl reload nginx

Firewall

Check Running Firewall Configuration

iptables -L

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the GNU GPLv3. See LICENSE for more information.

Acknowledgments

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages