• 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 OVH Using Terraform

Infrastructure as Code: Provisioning Secure PHP Clusters on OVH Using Terraform

OVHcloud Provider Configuration for Terraform

To provision resources on OVHcloud, we’ll leverage the official Terraform OVHcloud provider. This requires obtaining API credentials and configuring the provider block in your Terraform configuration. Ensure you have an OVHcloud account and have generated an Application Key and Secret. These credentials should be managed securely, ideally using environment variables or a secrets management system, rather than hardcoding them directly into your Terraform files.

The following snippet demonstrates the basic provider configuration. Replace placeholders with your actual credentials and desired region.

It’s highly recommended to use environment variables for sensitive information like API keys and secrets. Terraform will automatically pick these up if they are set.

# Set these environment variables before running Terraform
# export OVH_ENDPOINT="ovh-eu" # or "ovh-us", "ovh-ca"
# export OVH_APPLICATION_KEY="your_application_key"
# export OVH_APPLICATION_SECRET="your_application_secret"
# export OVH_CONSUMER_KEY="your_consumer_key"

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

provider "ovh" {
  endpoint = var.ovh_endpoint
}

variable "ovh_endpoint" {
  description = "OVHcloud API endpoint (e.g., ovh-eu, ovh-us, ovh-ca)"
  type        = string
  default     = "ovh-eu" # Default to European region
}

Designing the PHP Cluster Architecture

A robust PHP cluster typically involves several components: a load balancer, multiple web servers running PHP-FPM, and a shared database. For this example, we’ll focus on provisioning the load balancer and web servers. We’ll assume a separate managed database solution or a pre-existing database instance.

Our architecture will consist of:

  • An OVHcloud Load Balancer (LBaaS) to distribute incoming HTTP/S traffic.
  • A set of OVHcloud Public Cloud Instances (VMs) configured to run Nginx as a web server and PHP-FPM for processing PHP requests.
  • User Data scripts to automate the initial setup and configuration of these instances.

Provisioning the Load Balancer

The OVHcloud Load Balancer service (LBaaS) is crucial for distributing traffic across your web servers. We’ll define a public load balancer with an HTTP frontend and a backend pool pointing to our web server instances.

resource "ovh_lb" "php_cluster_lb" {
  name = "php-cluster-lb"
  region = var.ovh_region # e.g., "GRA" for Strasbourg
  description = "Load balancer for the PHP cluster"
}

resource "ovh_lb_frontend" "http_frontend" {
  lb_id = ovh_lb.php_cluster_lb.id
  name = "http-frontend"
  port = 80
  default_backend_id = ovh_lb_backend.web_backend.id
  allowed_sources = ["0.0.0.0/0"] # Allow traffic from anywhere
}

resource "ovh_lb_backend" "web_backend" {
  lb_id = ovh_lb.php_cluster_lb.id
  name = "web-backend"
  protocol = "http"
  port = 80
  method = "roundrobin" # Load balancing method
  # Health check configuration
  health_check {
    port = 80
    path = "/"
    interval = 5000 # milliseconds
    timeout = 1000 # milliseconds
    retries = 3
  }
}

Note that the `ovh_region` variable should be set to a valid OVHcloud region identifier (e.g., “GRA”, “SBG”, “BHS”).

Provisioning Web Server Instances

We’ll create a set of identical Public Cloud Instances. Each instance will be configured using a cloud-init script (passed via `user_data`) to install Nginx, PHP-FPM, and necessary PHP extensions, and to join the load balancer’s backend pool.

The `user_data` script is critical for automating the setup. It should handle package installation, Nginx and PHP-FPM configuration, and importantly, registering the instance with the load balancer’s backend pool. For simplicity, we’ll assume the load balancer’s IP is known or can be dynamically retrieved. In a production scenario, you might use a service discovery mechanism or a more sophisticated registration process.

resource "ovh_cloud_project_instance" "web_server" {
  count = var.instance_count # Number of web servers in the cluster

  service_name = var.ovh_service_name # Your OVHcloud Public Cloud project service name
  name         = "webserver-${count.index}"
  region       = var.ovh_region
  flavor_id    = var.instance_flavor_id # e.g., "s1-2"
  image_id     = var.instance_image_id # e.g., "ubuntu-2004"

  # SSH key for access
  ssh_key_name = var.ssh_key_name

  # User data for automated setup
  user_data = templatefile("${path.module}/cloud-init/webserver.yaml", {
    lb_backend_ip = ovh_lb.web_backend.private_ip # This might require a data source or different approach for dynamic IP
    lb_backend_port = ovh_lb.web_backend.port
    nginx_config_template = file("${path.module}/nginx/default.conf")
  })

  # Network configuration - attach to a private network if needed for DB access
  # network_id = var.private_network_id
}

variable "instance_count" {
  description = "Number of web server instances"
  type        = number
  default     = 3
}

variable "ovh_service_name" {
  description = "OVHcloud Public Cloud project service name"
  type        = string
  # default = "your-service-name" # Set this via tfvars or environment variable
}

variable "ovh_region" {
  description = "OVHcloud region for instances"
  type        = string
  default     = "GRA"
}

variable "instance_flavor_id" {
  description = "Flavor ID for instances (e.g., s1-2, b2-7)"
  type        = string
  default     = "s1-2"
}

variable "instance_image_id" {
  description = "Image ID for instances (e.g., ubuntu-2004, debian-11)"
  type        = string
  default     = "ubuntu-2004"
}

variable "ssh_key_name" {
  description = "Name of the SSH public key registered in OVHcloud"
  type        = string
  # default = "my-ssh-key" # Set this via tfvars or environment variable
}

Cloud-Init Script for Web Servers (cloud-init/webserver.yaml)

This cloud-init script will be executed on each new instance. It installs Nginx, PHP-FPM, and configures them. Crucially, it needs to register the instance with the load balancer. The `ovh_lb_backend_server` resource is used for this registration. The `user_data` script can dynamically add the instance’s IP to the backend pool. However, directly referencing `ovh_lb.web_backend.private_ip` within `user_data` is not directly supported as `user_data` is executed before Terraform fully provisions and outputs the LB’s private IP. A common pattern is to use a data source to fetch the LB’s IP or to have the LB’s IP available via an output that the cloud-init script can access (e.g., via a metadata service if available, or by passing it as a parameter during instance creation if the provider supported it directly in user_data). For this example, we’ll simulate passing the IP and focus on the installation part.

#cloud-config
package_update: true
packages:
  - nginx
  - php-fpm
  - php-mysql
  - php-mbstring
  - php-xml
  - php-gd
  - php-curl
  - php-zip
runcmd:
  # Configure Nginx to proxy to PHP-FPM
  - echo "${nginx_config_template}" > /etc/nginx/sites-available/default
  - sed -i 's/listen = \/run\/php\/php7.4-fpm.sock/listen = 127.0.0.1:9000/' /etc/php/7.4/fpm/pool.d/www.conf # Adjust PHP version as needed
  - systemctl restart nginx
  - systemctl restart php7.4-fpm # Adjust PHP version as needed

  # Register with Load Balancer Backend Pool (Conceptual - requires dynamic IP handling)
  # In a real-world scenario, you'd use Terraform's ovh_lb_backend_server resource
  # and ensure the instance's IP is correctly associated.
  # The following is a placeholder for how you might add a server if you had its IP.
  # You would typically use 'terraform apply' to create the ovh_lb_backend_server resource
  # after the instance is provisioned and its IP is known.
  # Example:
  # - echo "Adding server ${HOSTNAME} to LB backend pool..."
  # - # This part is best handled by Terraform's ovh_lb_backend_server resource
  # - # rather than directly in cloud-init for dynamic registration.
  # - # If you MUST do it here, you'd need a way to pass the LB IP and register.
  # - # For example, using a pre-defined script or API call.
  # - # Example using a hypothetical registration tool:
  # - # /usr/local/bin/register-lb-server --ip $(hostname -I | awk '{print $1}') --backend-id {{ .lb_backend_id }} --api-key {{ .ovh_api_key }}
write_files:
  - path: /var/www/html/index.php
    content: |
      <?php
      echo "Hello from " . gethostname();
      phpinfo();
      ?>
    permissions: '0644'


Registering Instances with the Load Balancer Backend

The `ovh_lb_backend_server` resource in Terraform is used to add individual servers to a backend pool. This is the correct, declarative way to manage which instances are part of your load-balanced service.

resource "ovh_lb_backend_server" "web_server_backend" {
  count = length(ovh_cloud_project_instance.web_server) # Create one for each instance

  lb_id = ovh_lb.php_cluster_lb.id
  backend_id = ovh_lb.web_backend.id
  address = ovh_cloud_project_instance.web_server[count.index].public_ip_address # Use the public IP
  status = "active" # Or "on" depending on provider version
  port = 80
  weight = 100 # Default weight
}

This resource ensures that each provisioned web server instance is automatically added to the load balancer's backend pool. Terraform manages the lifecycle of these backend servers, adding them when instances are created and removing them when instances are destroyed.

Nginx Configuration (nginx/default.conf)

A standard Nginx configuration for serving PHP applications via PHP-FPM. This file will be deployed to each web server instance.

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?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # With php-fpm (or other unix sockets):
        fastcgi_pass unix:/run/php/php7.4-fpm.sock; # Adjust PHP version as needed
        # With php-cgi (or other tcp sockets):
        # fastcgi_pass 127.0.0.1:9000;
    }

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

Security Considerations

Securing your PHP cluster involves several layers:

  • Network Security Groups/Firewalls: While not explicitly defined in these Terraform snippets, ensure you configure OVHcloud security groups or instance-level firewalls (e.g., `ufw`) to only allow necessary inbound traffic (e.g., port 80/443 to the load balancer, SSH to instances from trusted IPs).
  • SSH Key Management: Use strong SSH keys and restrict SSH access to authorized personnel.
  • HTTPS: For production environments, configure SSL/TLS termination at the load balancer. This can be done by creating an HTTPS frontend on the OVHcloud LB and uploading your SSL certificate.
  • PHP Security: Keep PHP updated, use secure coding practices, and consider security extensions like `php-security-advisories`.
  • Nginx Hardening: Configure Nginx to disable unnecessary modules, limit request sizes, and protect against common web vulnerabilities.
  • Instance Hardening: Regularly update OS packages and remove any unnecessary services from the instances.

Deployment Workflow

The standard Terraform workflow applies:

  • Initialize Terraform: terraform init
  • Review the execution plan: terraform plan
  • Apply the configuration: terraform apply
  • Destroy resources when no longer needed: terraform destroy

Ensure your OVHcloud API credentials and SSH key name are correctly configured (e.g., via environment variables or a .tfvars file).

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

  • How to securely integrate AWS S3 file uploads endpoints into WordPress custom plugins using Metadata API (add_post_meta)
  • Step-by-Step Guide to building a custom user session manager block for Gutenberg using Svelte standalone templates
  • Step-by-Step Guide to building a custom automated performance diagnostic log block for Gutenberg using REST API custom routes
  • How to securely integrate Firebase Realtime DB endpoints into WordPress custom plugins using Shortcode API
  • Step-by-Step Guide to building a custom dynamic lead collector block for Gutenberg using HTMX dynamic attributes

Categories

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

Recent Posts

  • How to securely integrate AWS S3 file uploads endpoints into WordPress custom plugins using Metadata API (add_post_meta)
  • Step-by-Step Guide to building a custom user session manager block for Gutenberg using Svelte standalone templates
  • Step-by-Step Guide to building a custom automated performance diagnostic log block for Gutenberg using REST API custom routes

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (810)
  • Debugging & Troubleshooting (586)
  • Security & Compliance (546)
  • SEO & Growth (492)
  • 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