🌐 Building a Linux Web Server with Terraform & Ansible – Part 4: Installing and Configuring Nginx with Ansible

In this article, we’ll install and configure Nginx as the web server for our project. Instead of configuring everything manually, we’ll use Ansible roles to make the process repeatable and clean.


📁 Step 1: Create the Nginx Role

Let’s start by creating a new role for our Nginx configuration:

mkdir -p roles/install-nginx/{tasks,handlers,templates}

Now add the role to your main.yml Ansible playbook:

# main.yml
...
  roles:
    - role: roles/security
    - role: roles/install-nginx

⚙️ Step 2: Add the Base Nginx Tasks

Create roles/install-nginx/tasks/main.yml:

---
- name: Install Nginx
  apt:
    name: nginx
    state: present
    force: yes
    update_cache: yes

- name: Symlink default site
  file:
    src: /etc/nginx/sites-available/default
    dest: /etc/nginx/sites-enabled/default
    state: link

- name: Configure http ufw for nginx
  ufw: rule=allow port=80 proto=tcp

- name: Configure https ufw for nginx
  ufw: rule=allow port=443 proto=tcp
  notify: Restart Nginx

🔍 Breaking It Down

Install Nginx: Installs the package with cache refresh.

Symlink default site: Makes sure Nginx loads the default config from sites-available.

UFW rules: Opens ports 80 (HTTP) and 443 (HTTPS) in the firewall.

Notify handler: Triggers a service restart if configs change.


🔁 Step 3: Add the Handlers

Create the handler file:
roles/install-nginx/handlers/main.yml

---
- name: Restart Nginx
  service: 
    name: nginx
    state: restarted

This ensures Nginx restarts whenever our configuration is updated.


🚀 Step 4: Run the Playbook

Run the updated playbook:

play main.yml

Then verify that Nginx is up and running:

curl <droplet ip>

You should see the default Nginx welcome page.


✍️ Step 5: Customize Nginx Configuration

  1. SSH into your server and copy the default config:
scp admin@<droplet-ip>:/etc/nginx/nginx.conf roles/install-nginx/templates/nginx.conf.j2

2. Edit relevant parts of the config to use Ansible variables:

user {{ username }};
worker_processes {{ cpu_count }};
  • username: Already defined in our playbook vars.
  • cpu_count: The number of CPU cores available.

You can check your core count on the server with:

nproc

🔁 Step 6: Tune the Events Section

Update the events block like this:

events {
    worker_connections {{ worker_connections }};
    multi_accept on;
}
  • worker_connections: Set to your system’s open file limit:
ulimit -n
  • multi_accept on: Allows a worker to accept multiple connections at once, improving performance under load.

🌀 Step 7: Add Gzip Compression

Update the http block to include:

gzip on;
gzip_proxied any;
gzip_comp_level 5;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

This enables compression for common static file types. Gzip reduces the size of HTTP responses and improves page load time—especially important for mobile or slower connections.


🧩 Step 8: Final nginx.conf Template

Here’s what your final nginx.conf.j2 should look like:

user {{ username }};
worker_processes {{ cpu_count }};
worker_cpu_affinity auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections {{ worker_connections }};
        multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        types_hash_max_size 2048;
        server_tokens build; # Recommended practice is to turn this off

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

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

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1
        ssl_prefer_server_ciphers off; # Don't force server cipher order.

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;

        ##
        # Gzip Settings
        ##

        gzip on;

        # gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 5;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

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

📤 Step 9: Upload the Config with Ansible

Update your role’s tasks/main.yml to include:

...
- name: Upload nginx.conf file
  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
  vars:
    worker_connections: 1024
    cpu_count: 1
  notify: Restart Nginx

Then re-run:

play main.yml

🎉 Summary

In this article, we:

  • Created a reusable Ansible role for Nginx
  • Installed and configured the base firewall rules
  • Customized nginx.conf with variables like username and cpu_count
  • Enabled Gzip compression and multi_accept for better performance
  • Verified that Nginx is working with our config

🔜 Next Up…

In the next part of the series, we’ll create a basic Flask Hello World app, serve it with Gunicorn, and integrate it into our Nginx configuration.

See you there! 👋

Leave a Reply

Your email address will not be published. Required fields are marked *