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

Infrastructure as Code: Provisioning Secure Magento 2 Clusters on Linode Using Terraform

Terraform Provider Configuration for Linode

To provision 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 dedicated secrets management system rather than hardcoding it directly into your Terraform configuration files.

Create a file named providers.tf and add the following configuration:

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
}

variable "region" {
  description = "The Linode region to deploy resources in."
  type        = string
  default     = "us-east"
}

variable "ssh_public_key" {
  description = "The public SSH key to deploy for root access."
  type        = string
}

variable "magento_domain" {
  description = "The domain name for the Magento installation."
  type        = string
  default     = "example.com"
}

variable "magento_db_password" {
  description = "Password for the Magento database user."
  type        = string
  sensitive   = true
  default     = "supersecretpassword"
}

You can set the linode_api_token and ssh_public_key environment variables before running Terraform commands, or create a terraform.tfvars file (ensure this file is not committed to version control if it contains sensitive data):

# terraform.tfvars
linode_api_token = "your_linode_api_token_here"
ssh_public_key   = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD..."

Provisioning Database Servers (MySQL)

For a secure and performant Magento 2 cluster, a dedicated database server is essential. We’ll provision a Linode Instance for MySQL. For production environments, consider using Linode’s managed database services for enhanced reliability and scalability, but for this example, we’ll set up a self-managed MySQL instance.

Create a file named database.tf:

resource "linode_instance" "magento_db" {
  label = "magento-db-server"
  image = "ubuntu/22.04"
  region = var.region
  type = "g6-nanode-1" # Adjust instance type based on expected load
  root_pass = random_password.db_root_password.result
  authorized_keys = [
    file("~/.ssh/id_rsa.pub") # Ensure your SSH key is present
  ]
  tags = ["magento", "database"]

  connection {
    type        = "ssh"
    user        = "root"
    private_key = file("~/.ssh/id_rsa") # Path to your private SSH key
    host        = self.ip_address
    timeout     = "5m"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update -y",
      "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server",
      "sudo systemctl start mysql",
      "sudo systemctl enable mysql",
      "sudo mysql -e \"ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '${random_password.db_root_password.result}';\"",
      "sudo mysql -e \"CREATE DATABASE IF NOT EXISTS magento_db;\"",
      "sudo mysql -e \"CREATE USER IF NOT EXISTS magento_user@'%' IDENTIFIED BY '${var.magento_db_password}';\"",
      "sudo mysql -e \"GRANT ALL PRIVILEGES ON magento_db.* TO 'magento_user'@'%';\"",
      "sudo mysql -e \"FLUSH PRIVILEGES;\"",
      "sudo sed -i 's/^bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf", # WARNING: For security, restrict this to specific IPs in production
      "sudo systemctl restart mysql"
    ]
  }
}

resource "random_password" "db_root_password" {
  length           = 16
  special          = true
  override_special = "_%@"
}

output "magento_db_ip" {
  description = "The public IP address of the Magento database server."
  value       = linode_instance.magento_db.ip_address
}

output "magento_db_root_password" {
  description = "The root password for the Magento database server."
  value       = random_password.db_root_password.result
  sensitive   = true
}

Security Note: Binding MySQL to 0.0.0.0 is insecure for production. In a real-world scenario, you would configure the bind-address to the private IP of the database server and restrict access from the web server IPs only. This is typically achieved by configuring Linode’s firewall or using security groups.

Provisioning Web Servers (Nginx + PHP-FPM)

We’ll provision two identical web servers for redundancy and load balancing. Each server will run Nginx as the web server and PHP-FPM for executing PHP code.

Create a file named webservers.tf:

resource "linode_instance" "magento_web" {
  count = 2 # Provision two web servers

  label = "magento-web-${count.index + 1}"
  image = "ubuntu/22.04"
  region = var.region
  type = "g6-nanode-2" # Adjust instance type based on expected load
  root_pass = random_password.web_root_password[count.index].result
  authorized_keys = [
    file("~/.ssh/id_rsa.pub")
  ]
  tags = ["magento", "web"]

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

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update -y",
      "sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx php-fpm php-mysql php-curl php-gd php-mbstring php-xml php-zip php-intl",
      "sudo systemctl start nginx",
      "sudo systemctl enable nginx",
      "sudo systemctl start php8.1-fpm", # Adjust PHP version as needed
      "sudo systemctl enable php8.1-fpm",
      # Configure Nginx for Magento (basic setup)
      "sudo rm /etc/nginx/sites-available/default",
      "sudo tee /etc/nginx/sites-available/magento.conf <<EOF
server {
    listen 80;
    server_name ${var.magento_domain};
    root /var/www/html/magento; # Magento will be installed here later

    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
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
        include fastcgi_params;
    }

    # Deny access to sensitive files
    location ~* /(composer\.json|composer\.lock|\.env|\.htaccess|app/etc/env\.php|var/cache|var/session) {
        deny all;
        return 404;
    }
}
EOF",
      "sudo ln -s /etc/nginx/sites-available/magento.conf /etc/nginx/sites-enabled/",
      "sudo nginx -t",
      "sudo systemctl reload nginx"
    ]
  }
}

resource "random_password" "web_root_password" {
  count            = 2
  length           = 16
  special          = true
  override_special = "_%@"
}

output "magento_web_ips" {
  description = "The public IP addresses of the Magento web servers."
  value       = linode_instance.magento_web[*].ip_address
}

Load Balancer Configuration

A load balancer is crucial for distributing traffic across the web servers and providing a single point of access. We’ll use a Linode NodeBalancer.

Create a file named loadbalancer.tf:

resource "linode_nodebalancer" "magento_lb" {
  label    = "magento-lb"
  region   = var.region
  client_conn_throttle = 100 # Adjust as needed

  # Basic health check configuration
  health_check {
    protocol = "tcp"
    port     = 80
    check    = "/status" # A simple HTTP check endpoint
    interval = 10
    timeout  = 5
    unhealthy_threshold = 3
    healthy_threshold   = 3
  }
}

resource "linode_nodebalancer_node" "magento_web_nodes" {
  count = length(linode_instance.magento_web)

  label         = "web-node-${count.index + 1}"
  nodebalancer_id = linode_nodebalancer.magento_lb.id
  address       = linode_instance.magento_web[count.index].ip_address
  port          = 80
  weight        = 100 # Equal weighting
}

resource "linode_nodebalancer_config" "magento_lb_config" {
  nodebalancer_id = linode_nodebalancer.magento_lb.id
  port            = 80
  protocol        = "http"
  algorithm       = "roundrobin"
  stickiness      = "table" # Session persistence for Magento
}

output "magento_lb_ip" {
  description = "The public IP address of the Magento NodeBalancer."
  value       = linode_nodebalancer.magento_lb.ipv4
}

Note on Health Checks: The health check check = "/status" assumes you will create a simple PHP file (e.g., /var/www/html/status.php) on your web servers that outputs a 200 OK response. This is a basic check; for production, consider more robust checks.

Magento 2 Installation and Configuration

The final step is to install Magento 2 on the web servers and configure it to use the database. This can be done using a combination of Terraform’s remote-exec provisioner and shell scripts. For more complex deployments, consider using Ansible or a similar configuration management tool orchestrated by Terraform.

We’ll add a provisioner to the webservers.tf file. For simplicity, this example installs Magento on the first web server. In a production setup, you’d want to ensure consistency across all web servers, potentially using shared storage (like NFS) or a deployment pipeline.

# Add this to the linode_instance "magento_web" resource in webservers.tf

  provisioner "remote-exec" {
    inline = [
      # ... (previous commands for Nginx and PHP-FPM) ...

      # Magento Installation (on the first web server)
      "if [ \"${count.index}\" = \"0\" ]; then",
      "  sudo apt-get install -y composer git unzip wget",
      "  sudo mkdir -p /var/www/html/magento",
      "  sudo chown www-data:www-data /var/www/html/magento",
      "  cd /var/www/html/magento",
      "  sudo -u www-data composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition .",
      "  sudo -u www-data php bin/magento setup:install --base-url=http://${var.magento_domain}/ \\",
      "    --db-host=${linode_instance.magento_db.ip_address} \\",
      "    --db-name=magento_db \\",
      "    --db-user=magento_user \\",
      "    --db-password='${var.magento_db_password}' \\",
      "    --admin-user=admin \\",
      "    --admin-password=AdminPassword123 \\", # CHANGE THIS IN PRODUCTION
      "    --admin-email=admin@${var.magento_domain} \\",
      "    --language=en_US \\",
      "    --currency=USD \\",
      "    --timezone=America/New_York \\",
      "    --use-rewrites-module=1",
      "  sudo -u www-data php bin/magento setup:upgrade",
      "  sudo -u www-data php bin/magento cache:enable",
      "  sudo -u www-data php bin/magento indexer:reindex",
      "  sudo chmod -R 775 var pub/static pub/media app/etc", # Adjust permissions as needed
      "  sudo chown -R www-data:www-data var pub/static pub/media app/etc",
      "  sudo systemctl restart php8.1-fpm", # Adjust PHP version
      "  sudo systemctl reload nginx",
      "fi"
    ]
  }

Important Considerations for Production:

  • Database Security: Restrict the MySQL user’s access to only the web server’s private IP address instead of ‘%’.
  • Admin Credentials: Use strong, unique passwords for the Magento admin user and ensure they are managed securely.
  • Composer Authentication: For private Magento repositories or extensions, you’ll need to configure Composer authentication.
  • Shared Storage: For a truly scalable Magento setup, consider using a shared filesystem (e.g., NFS, Linode Object Storage with S3-compatible access) for pub/media and var/view_preprocessed to ensure consistency across web nodes.
  • HTTPS: This example uses HTTP. For production, you must configure SSL/TLS, likely using Let’s Encrypt, and update Nginx and the NodeBalancer to use HTTPS.
  • Caching: Integrate Redis or Memcached for improved performance.
  • Deployment Strategy: For frequent updates, implement a CI/CD pipeline that deploys code to all web servers consistently.
  • Firewall: Configure Linode’s firewall to restrict access to necessary ports only.

Running Terraform

Once you have your Terraform files organized (e.g., in a directory named magento-iac), navigate to that directory in your terminal and run the following commands:

cd magento-iac
terraform init
terraform plan
terraform apply

terraform init initializes the Terraform workspace and downloads the necessary provider plugins.

terraform plan shows you a preview of the infrastructure changes that will be made.

terraform apply provisions the resources on Linode. You will be prompted to confirm the changes.

After the apply is complete, you can access your Magento 2 installation via the NodeBalancer’s IP address (outputted as magento_lb_ip).

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