• 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 PHP Clusters on Linode Using Terraform

Infrastructure as Code: Provisioning Secure PHP 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 rather than hardcoding it directly into your Terraform configuration files. This prevents accidental exposure of sensitive credentials in version control.

Create a file named main.tf and add the following provider configuration. Replace YOUR_LINODE_API_TOKEN with your actual Linode API token, or preferably, set it as an environment variable named LINODE_TOKEN.

terraform {
  required_providers {
    linode = {
      source  = "linode/linode"
      version = "~> 1.0"
    }
  }
}

provider "linode" {
  token = var.linode_api_token
}

variable "linode_api_token" {
  description = "Linode API Token"
  type        = string
  sensitive   = true
  default     = "" # If not using environment variable, uncomment and set here, but NOT recommended for production
}

# Example of how to set the variable from an environment variable
# terraform {
#   ...
#   backend "remote" {
#     ...
#   }
# }
#
# variable "linode_api_token" {
#   description = "Linode API Token"
#   type        = string
#   sensitive   = true
# }
#
# provider "linode" {
#   token = lookup(var.linode_api_token, "LINODE_TOKEN", "") # This syntax is illustrative; actual env var usage is via `TF_VAR_linode_api_token` or `terraform.tfvars`
# }

To use an environment variable, set it in your shell before running Terraform commands:

export LINODE_TOKEN="YOUR_LINODE_API_TOKEN"

Then, in your main.tf, you can reference it like this:

provider "linode" {
  token = var.linode_api_token
}

variable "linode_api_token" {
  description = "Linode API Token"
  type        = string
  sensitive   = true
  default     = env("LINODE_TOKEN") # This is the correct way to reference env vars for variables
}

Provisioning a Secure PHP Cluster

A robust PHP cluster typically involves multiple components: a load balancer, web servers running PHP-FPM, and potentially a database. For this example, we’ll focus on provisioning the web servers and a basic Nginx setup for load balancing. We’ll use a custom user data script to configure Nginx and PHP-FPM upon instance boot.

First, let’s define the Linode instances that will serve our PHP application. We’ll create a count-based resource to spin up multiple web servers. Each server will have a public IP address and will be configured with a standard Ubuntu LTS image.

resource "linode_instance" "php_web_server" {
  count           = 3 # Number of web servers
  label           = "php-web-${count.index + 1}"
  region          = "us-east" # Choose your preferred region
  type            = "g6-nanode-1" # Choose an appropriate instance type
  image           = "linode/ubuntu22.04"
  root_pass       = random_password.root_password[count.index].result
  authorized_keys = [
    # Add your SSH public keys here for secure access
    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD..."
  ]

  user_data = templatefile("${path.module}/scripts/bootstrap.sh", {
    server_id = count.index + 1
    # Add any other variables needed by your bootstrap script
  })

  tags = ["php-cluster", "webserver"]

  lifecycle {
    create_before_destroy = true
  }
}

resource "random_password" "root_password" {
  count   = 3
  length  = 16
  special = true
}

output "web_server_ips" {
  description = "Public IP addresses of the PHP web servers"
  value       = linode_instance.php_web_server[*].ip_address
}

Bootstrap Script for Nginx and PHP-FPM

The user_data script is critical for automating the setup of each web server. This script will install Nginx, PHP-FPM, and configure them to serve your application. It will also set up a basic firewall for security.

Create a directory named scripts in the same directory as your main.tf file. Inside scripts, create a file named bootstrap.sh with the following content:

#!/bin/bash

# --- Security Updates and Package Installation ---
apt-get update -y
apt-get upgrade -y
apt-get install -y nginx php-fpm php-mysql php-mbstring php-xml php-gd php-curl unzip

# --- Firewall Configuration ---
ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw --force enable

# --- Nginx Configuration ---
# Remove default Nginx configuration
rm /etc/nginx/sites-available/default
rm /etc/nginx/sites-enabled/default

# Create a new Nginx site configuration
cat <<EOF > /etc/nginx/sites-available/php-app
server {
    listen 80;
    server_name _; # Listen on all hostnames

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

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version if necessary
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    location ~ /\.ht {
        deny all;
    }
}
EOF

# Enable the new Nginx site
ln -s /etc/nginx/sites-available/php-app /etc/nginx/sites-enabled/

# Test Nginx configuration and reload
nginx -t
systemctl reload nginx

# --- PHP-FPM Configuration (Optional: Adjust if needed) ---
# Ensure PHP-FPM is running and enabled
systemctl enable php8.1-fpm # Adjust PHP version if necessary
systemctl start php8.1-fpm

# --- Application Deployment Placeholder ---
# In a real-world scenario, you would deploy your application code here.
# This could involve git clone, rsync, or a deployment tool.
# For demonstration, we'll create a simple index.php
mkdir -p /var/www/html
chown www-data:www-data /var/www/html
cat <<EOF > /var/www/html/index.php
<?php
echo "Hello from PHP server ID: ${server_id}!";
phpinfo();
?>
EOF
chown www-data:www-data /var/www/html/index.php

echo "Bootstrap script finished."

Load Balancer Configuration

For a production-ready cluster, a dedicated load balancer is essential. Linode offers managed Load Balancers, which are the simplest and most robust option. We’ll provision a Linode Load Balancer and point it to our web servers.

resource "linode_loadbalancer" "php_lb" {
  label  = "php-app-lb"
  region = "us-east" # Must match web server region
  vpc_id = linode_instance.php_web_server[0].vpc_id # Associate with the VPC of one of the web servers

  # Configure HTTP listener
  listener {
    protocol = "http"
    port     = 80
    target {
      protocol = "http"
      port     = 80
      # Use the IP addresses of the web servers as targets
      # The 'count' index here is crucial to map to the correct web server IPs
      target_nodes = [
        for i in range(length(linode_instance.php_web_server)) : linode_instance.php_web_server[i].id
      ]
    }
    ssl_certificate_id = null # For HTTP, no certificate needed
  }

  # Optional: Configure HTTPS listener (requires a certificate)
  # listener {
  #   protocol = "https"
  #   port     = 443
  #   target {
  #     protocol = "http" # Or "https" if your web servers handle SSL termination
  #     port     = 80
  #     target_nodes = [
  #       for i in range(length(linode_instance.php_web_server)) : linode_instance.php_web_server[i].id
  #     ]
  #   }
  #   ssl_certificate_id = linode_certificate.my_cert.id # Assuming you have a Linode Certificate resource defined
  # }

  tags = ["php-cluster", "loadbalancer"]
}

output "load_balancer_ip" {
  description = "The public IP address of the Linode Load Balancer"
  value       = linode_loadbalancer.php_lb.ip
}

In this configuration, the load balancer will distribute incoming HTTP traffic across the three web servers. The target_nodes attribute dynamically references the IDs of the provisioned web server instances. If you intend to use HTTPS, you would need to provision a Linode Certificate resource and reference its ID in the HTTPS listener configuration.

Database Provisioning (Optional but Recommended)

For a production PHP application, a separate database is almost always required. Linode offers managed MySQL and PostgreSQL databases, which are highly recommended for ease of management and reliability. Here’s how you can provision a managed MySQL database:

resource "linode_database" "php_db" {
  label       = "php-app-db"
  region      = "us-east" # Match your application region
  engine      = "mysql"
  version     = "8.0"
  plan        = "db-s1-mysql-1vcpu-1gb" # Choose an appropriate plan
  replication = false # Set to true for high availability

  # Security: Use a strong, randomly generated password
  password = random_password.db_password.result
  username = "appuser"

  tags = ["php-cluster", "database"]
}

resource "random_password" "db_password" {
  length  = 20
  special = true
}

output "db_host" {
  description = "The hostname of the managed database"
  value       = linode_database.php_db.host
}

output "db_port" {
  description = "The port of the managed database"
  value       = linode_database.php_db.port
}

output "db_username" {
  description = "The username for the managed database"
  value       = linode_database.php_db.username
}

output "db_password" {
  description = "The password for the managed database"
  value       = linode_database.php_db.password
  sensitive   = true
}

You would then update your PHP application’s database connection configuration to use these output values. For security, it’s best to inject these credentials into your application via environment variables or a secrets management system rather than hardcoding them.

Deployment Workflow

With your Terraform configuration in place, the deployment workflow is straightforward:

  • Initialize Terraform: Run terraform init in your project directory. This downloads the Linode provider and any other necessary plugins.
  • Review the Plan: Execute terraform plan. This command shows you exactly what infrastructure Terraform will create, modify, or destroy. Carefully review this output to ensure it matches your expectations.
  • Apply the Configuration: Run terraform apply. Terraform will prompt you to confirm the changes. Type yes to proceed with provisioning the Linode resources.
  • Access Your Application: Once the apply is complete, Terraform will output the IP address of your load balancer. You can access your PHP application by navigating to this IP address in your web browser. You should see the “Hello from PHP server ID: X!” message.
  • Destroy Resources: When you no longer need the infrastructure, run terraform destroy to tear down all provisioned resources and avoid ongoing costs.

This setup provides a scalable, secure, and automated way to deploy PHP applications on Linode, leveraging the power of Infrastructure as Code.

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

  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments
  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive
  • Leveraging PHP 8’s JIT Compiler and Vector APIs for Extreme Web Application Performance
  • Leveraging PHP 8 JIT and AWS Lambda for High-Performance, Serverless WordPress REST API Backends
  • Beyond the Basics: Leveraging PHP 8.3’s JIT Compiler and Fibers for High-Concurrency Laravel Applications

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (15)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (20)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (26)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments
  • Migrating Legacy WordPress to Headless with Laravel: A Performance and Security Deep Dive
  • Leveraging PHP 8's JIT Compiler and Vector APIs for Extreme Web Application Performance

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

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