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

Vengala Vinay

Having 9+ 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 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala