• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Infrastructure as Code: Provisioning Secure WooCommerce Clusters on Linode Using Terraform

Infrastructure as Code: Provisioning Secure WooCommerce Clusters on Linode Using Terraform

Terraform Provider Configuration for Linode

To begin provisioning infrastructure on Linode using Terraform, we first need to configure the Linode provider. This involves specifying your Linode API token. It’s crucial to manage this token securely, ideally using environment variables or a secrets management system rather than hardcoding it directly into your Terraform configuration files.

Create a file named providers.tf in your Terraform project directory. This file will house the provider configuration.

Securing the Linode API Token

The most secure method for handling your Linode API token is via an environment variable. Before running any Terraform commands, set the LINODE_TOKEN environment variable in your shell:

export LINODE_TOKEN="your_linode_api_token_here"

Replace your_linode_api_token_here with your actual Linode API token. You can generate this token from your Linode Cloud Manager under your account settings.

Defining the Linode Provider Block

Now, within your providers.tf file, define the Linode provider block. Terraform will automatically pick up the LINODE_TOKEN environment variable.

terraform {
  required_providers {
    linode = {
      source  = "linode/linode"
      version = "~> 1.0" # Specify a version constraint
    }
  }
}

provider "linode" {
  # The LINODE_TOKEN environment variable will be used automatically.
  # Alternatively, you could explicitly set it here, but this is less secure:
  # token = var.linode_api_token
}

# Optional: Define a variable for the token if you prefer to pass it via a .tfvars file
# variable "linode_api_token" {
#   description = "Linode API Token"
#   type        = string
#   sensitive   = true
# }

The required_providers block ensures that Terraform knows which providers to download and use, along with version constraints for reproducibility. The provider "linode" block configures the provider itself. By not explicitly setting the token argument, Terraform defaults to using the LINODE_TOKEN environment variable.

Provisioning a Secure WooCommerce Cluster: Core Components

A robust WooCommerce cluster typically involves several key components: a load balancer, multiple web servers running PHP-FPM and Nginx, and a managed database. We’ll use Terraform to provision these resources on Linode.

Defining Network Resources: VPC and Subnets

For enhanced security and network isolation, we’ll deploy our cluster within a Virtual Private Cloud (VPC). This allows us to control network traffic precisely.

Create a file named network.tf.

resource "linode_vpc" "woocommerce_vpc" {
  description = "VPC for WooCommerce cluster"
  region      = "us-east" # Choose your preferred region
  label       = "woocommerce-vpc"
}

resource "linode_vpc_subnet" "app_subnet" {
  vpc_id      = linode_vpc.woocommerce_vpc.id
  label       = "app-subnet"
  ipv4_τητα  = "10.0.1.0/24" # Private subnet for application servers
  region      = linode_vpc.woocommerce_vpc.region
}

resource "linode_vpc_subnet" "db_subnet" {
  vpc_id      = linode_vpc.woocommerce_vpc.id
  label       = "db-subnet"
  ipv4_τητα  = "10.0.2.0/24" # Private subnet for database servers
  region      = linode_vpc.woocommerce_vpc.region
}

Here, we define a VPC and two private subnets: one for application servers and another for the database. Using private subnets ensures that these resources are not directly accessible from the public internet, enhancing security. The IP ranges 10.0.1.0/24 and 10.0.2.0/24 are examples; adjust them as needed for your network design.

Provisioning the Managed Database (MySQL)

A managed database service is essential for reliability and ease of maintenance. Linode offers managed MySQL databases.

Add the following to your database.tf file:

resource "linode_database" "woocommerce_db" {
  type        = "mysql"
  engine      = "mysql-8.0" # Specify desired MySQL version
  region      = linode_vpc.woocommerce_vpc.region
  label       = "woocommerce-db"
  description = "Managed MySQL for WooCommerce"
  plan        = "db-s-1vcpu-2gb" # Choose an appropriate plan
  replicas    = 1 # For high availability, consider increasing replicas
  vpc_id      = linode_vpc.woocommerce_vpc.id
  subnet_id   = linode_vpc_subnet.db_subnet.id
  password    = var.db_password # Use a secure password variable
  username    = "wp_user"
  allow_list = [
    linode_vpc_subnet.app_subnet.ipv4_τητα # Allow access from app subnet
  ]
}

variable "db_password" {
  description = "Password for the database user"
  type        = string
  sensitive   = true
}

This resource block defines a managed MySQL instance. We specify the type, engine, region, plan, and importantly, associate it with our VPC and the dedicated database subnet. The allow_list is critical for security; it restricts incoming connections to only those originating from the application subnet, preventing unauthorized access.

You’ll need to define the db_password variable. For production, this should be managed securely, perhaps via a .tfvars file that is not committed to version control, or through a secrets manager.

Provisioning Web Servers (Nginx + PHP-FPM)

We’ll deploy multiple Linode compute instances to act as our web servers. These will run Nginx as the web server and PHP-FPM for processing PHP requests. Using multiple instances behind a load balancer provides scalability and redundancy.

Create a file named webservers.tf.

resource "linode_instance" "web_server" {
  count           = 2 # Number of web server instances
  label           = "webserver-${count.index}"
  region          = linode_vpc.woocommerce_vpc.region
  type            = "g6-nanode-1" # Choose an appropriate instance type
  image           = "linode/ubuntu22.04"
  root_pass       = var.root_password # Secure root password
  authorized_keys = [var.ssh_public_key] # For SSH access
  vpc_id          = linode_vpc.woocommerce_vpc.id
  subnet_id       = linode_vpc_subnet.app_subnet.id

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file(var.ssh_private_key_path)
    host        = self.ip_address
    timeout     = "5m"
  }

  provisioner "remote-exec" {
    inline = [
      "apt update -y",
      "apt install -y nginx php-fpm php-mysql php-gd php-xml php-mbstring php-curl php-zip",
      "systemctl enable nginx",
      "systemctl enable php8.1-fpm", # Adjust PHP version if necessary
      "ufw allow 'Nginx Full'",
      "ufw allow 'Nginx HTTP'",
      "ufw allow 'Nginx HTTPS'",
      "ufw allow ssh",
      "ufw --force enable",
      # Configure Nginx and PHP-FPM (details below)
    ]
  }

  tags = ["webserver", "woocommerce"]
}

variable "root_password" {
  description = "Root password for Linode instances"
  type        = string
  sensitive   = true
}

variable "ssh_public_key" {
  description = "Public SSH key for accessing instances"
  type        = string
}

variable "ssh_private_key_path" {
  description = "Path to the private SSH key"
  type        = string
}

This block defines a collection of web server instances using the count meta-argument. Each instance is placed in the application subnet. The connection block configures how Terraform connects to the instances via SSH. The remote-exec provisioner is used to install Nginx, PHP-FPM, and necessary PHP extensions. It also configures the firewall (UFW) to allow necessary traffic.

Configuring Nginx and PHP-FPM

The remote-exec provisioner is a good starting point, but for production, it’s often better to use configuration management tools (like Ansible, Chef, or Puppet) or to bake custom images. For this example, we’ll use Terraform’s template_file data source to generate Nginx and PHP-FPM configuration files and then use remote-exec to upload and apply them.

Create a file named nginx.conf.tpl in a templates subdirectory:

# /templates/nginx.conf.tpl
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;
    index index.php index.html index.htm;

    server_name _; # Catch-all server name

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Make sure this matches your PHP-FPM pool configuration
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version if needed
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to sensitive files
    location ~ /\.ht {
        deny all;
    }

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

Now, update your webservers.tf to include the template rendering and file upload:

# ... (previous linode_instance resource) ...

resource "linode_instance" "web_server" {
  count           = 2
  label           = "webserver-${count.index}"
  region          = linode_vpc.woocommerce_vpc.region
  type            = "g6-nanode-1"
  image           = "linode/ubuntu22.04"
  root_pass       = var.root_password
  authorized_keys = [var.ssh_public_key]
  vpc_id          = linode_vpc.woocommerce_vpc.id
  subnet_id       = linode_vpc_subnet.app_subnet.id

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file(var.ssh_private_key_path)
    host        = self.ip_address
    timeout     = "5m"
  }

  provisioner "remote-exec" {
    inline = [
      "apt update -y",
      "apt install -y nginx php-fpm php-mysql php-gd php-xml php-mbstring php-curl php-zip",
      "systemctl enable nginx",
      "systemctl enable php8.1-fpm",
      "ufw allow 'Nginx Full'",
      "ufw allow 'Nginx HTTP'",
      "ufw allow 'Nginx HTTPS'",
      "ufw allow ssh",
      "ufw --force enable",
      # Create web root directory
      "mkdir -p /var/www/html",
      "chown www-data:www-data /var/www/html",
    ]
  }

  # Upload Nginx configuration
  provisioner "file" {
    content = templatefile("${path.module}/templates/nginx.conf.tpl", {
      # Pass variables to the template if needed
    })
    destination = "/etc/nginx/sites-available/default"
  }

  # Upload PHP-FPM configuration (optional, but good for tuning)
  # You might want to create a php-fpm.conf.tpl and upload it to /etc/php/8.1/fpm/php-fpm.conf
  # For simplicity, we'll assume default PHP-FPM pool settings are sufficient for now.

  provisioner "remote-exec" {
    inline = [
      "systemctl restart nginx",
      "systemctl restart php8.1-fpm", # Adjust PHP version if needed
    ]
  }

  tags = ["webserver", "woocommerce"]
}

# ... (variable definitions) ...

The templatefile function renders the nginx.conf.tpl, substituting any variables defined within it. The provisioner "file" then uploads this rendered configuration to the default Nginx site configuration path on the remote server. Finally, Nginx and PHP-FPM are restarted to apply the changes.

Provisioning the Load Balancer

A load balancer distributes incoming traffic across the web servers, ensuring high availability and scalability. Linode’s NodeBalancers are a good choice.

Create a file named loadbalancer.tf.

resource "linode_nodebalancer" "woocommerce_lb" {
  label       = "woocommerce-lb"
  region      = linode_vpc.woocommerce_vpc.region
  client_conn_throttle = 100 # Adjust as needed
  ipv4        = linode_vpc.woocommerce_vpc.ipv4_τητα # Assign public IPv4 from VPC
  # ipv6 = linode_vpc.woocommerce_vpc.ipv6_τητα # Optionally assign public IPv6

  # Health check configuration
  health_check {
    protocol = "tcp"
    port     = 80
    check    = "/index.php" # A simple check to ensure PHP is running
    interval = 5
    timeout  = 3
    unhealthy_threshold = 3
    healthy_threshold   = 3
  }

  # Frontend configuration (HTTP)
  frontend {
    protocol    = "http"
    port        = 80
    ssl_certificate_id = null # Configure SSL later
    ssl_key     = null
  }

  # Frontend configuration (HTTPS) - requires SSL certificate
  # frontend {
  #   protocol    = "https"
  #   port        = 443
  #   ssl_certificate_id = var.linode_ssl_certificate_id # Reference your Linode SSL cert ID
  #   ssl_key     = null
  # }

  # Backend configuration (referencing web servers)
  # We will dynamically add web servers to the NodeBalancer
}

# Resource to associate web servers with the NodeBalancer
resource "linode_nodebalancer_node" "web_nodes" {
  count             = length(linode_instance.web_server)
  nodebalancer_id   = linode_nodebalancer.woocommerce_lb.id
  address           = linode_instance.web_server[count.index].ip_address
  port              = 80
  label             = "webserver-${count.index}"
  weight            = 100
  mode              = "active"
  # Use the private IP address for internal communication
}

# Optional: Add a public IP to the VPC if not already assigned
resource "linode_vpc_ipv4_address" "lb_public_ip" {
  vpc_id    = linode_vpc.woocommerce_vpc.id
  subnet_id = linode_vpc_subnet.app_subnet.id # Assign to app subnet for simplicity
  public    = true
}

# Update NodeBalancer to use the newly assigned public IP
resource "linode_nodebalancer" "woocommerce_lb" {
  # ... (previous attributes) ...
  ipv4 = linode_vpc_ipv4_address.lb_public_ip.address
  # ... (rest of the attributes) ...
}

This configuration defines a NodeBalancer and then dynamically adds each web server instance as a backend node. The health check ensures that traffic is only sent to healthy web servers. We also assign a public IPv4 address from the VPC to the NodeBalancer.

Securing the Cluster: Firewall and SSL

Security is paramount for any e-commerce platform. We’ve already configured UFW on the web servers to allow only necessary ports. For the NodeBalancer, we’ll focus on SSL termination.

SSL Termination on the Load Balancer

For production, you should obtain an SSL certificate (e.g., from Let’s Encrypt or a commercial CA) and upload it to Linode. You can then reference its ID in the NodeBalancer configuration.

First, upload your certificate and key to Linode via the Cloud Manager or API. Let’s assume you have a certificate ID, which you would pass as a variable:

variable "linode_ssl_certificate_id" {
  description = "ID of the Linode SSL Certificate to use for HTTPS"
  type        = number
  default     = null # Set to your certificate ID in production
}

# Update the NodeBalancer resource in loadbalancer.tf:
resource "linode_nodebalancer" "woocommerce_lb" {
  # ... (previous attributes) ...

  frontend {
    protocol    = "http"
    port        = 80
    ssl_certificate_id = null
    ssl_key     = null
  }

  frontend {
    protocol    = "https"
    port        = 443
    ssl_certificate_id = var.linode_ssl_certificate_id
    ssl_key     = null # Linode manages the key when using a certificate ID
  }
}

With this, the NodeBalancer will handle SSL termination, decrypting HTTPS traffic before forwarding it to the web servers over HTTP (within the private VPC network). This simplifies certificate management on the web servers.

Deploying WooCommerce

Once the infrastructure is provisioned, you’ll need to deploy WooCommerce. This typically involves:

  • Downloading WordPress and WooCommerce.
  • Configuring WordPress wp-config.php with database credentials.
  • Setting up Nginx virtual hosts for your domain.
  • Securing the installation (e.g., changing default keys, setting strong passwords).

This deployment step is often best handled by a separate configuration management tool or a CI/CD pipeline. However, you could use Terraform’s remote-exec provisioner for a basic setup, though it’s not recommended for complex deployments.

Terraform Workflow

To apply this infrastructure:

  • Initialize Terraform: terraform init
  • Review the plan: terraform plan -var="db_password=your_secure_db_password" -var="root_password=your_secure_root_password" -var="ssh_public_key=ssh-rsa AAAAB3NzaC1yc2E..." -var="ssh_private_key_path=~/.ssh/id_rsa"
  • Apply the configuration: terraform apply -var="db_password=your_secure_db_password" -var="root_password=your_secure_root_password" -var="ssh_public_key=ssh-rsa AAAAB3NzaC1yc2E..." -var="ssh_private_key_path=~/.ssh/id_rsa"

Remember to replace placeholder values with your actual sensitive data and SSH keys. For production, use a .tfvars file for variables and ensure it’s not committed to version control.

Destroying the Infrastructure

When you no longer need the cluster, you can destroy all provisioned resources:

terraform destroy -var="db_password=your_secure_db_password" -var="root_password=your_secure_root_password" -var="ssh_public_key=ssh-rsa AAAAB3NzaC1yc2E..." -var="ssh_private_key_path=~/.ssh/id_rsa"

This comprehensive Terraform setup provides a robust, scalable, and secure foundation for your WooCommerce cluster on Linode.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala