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

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

OVH Terraform Provider Configuration

To provision resources on OVHcloud using Terraform, we first need to configure the OVH provider. This involves specifying your OVH API credentials and the region where you intend to deploy your infrastructure. It’s crucial to manage these credentials securely, ideally using environment variables or a dedicated secrets management system rather than hardcoding them directly into your Terraform configuration.

The OVH provider requires an Application Key, Application Secret, Consumer Key, and the endpoint URL. You can obtain these credentials by registering an application within the OVHcloud Control Panel under “My Project” -> “API & Automation”.

`main.tf` – Provider Block

# main.tf

terraform {
  required_providers {
    ovh = {
      source  = "ovh/ovh"
      version = "~> 1.0" # Pin to a specific major version for stability
    }
  }
}

provider "ovh" {
  # Use environment variables for sensitive credentials
  # export OVH_APPLICATION_KEY="YOUR_APP_KEY"
  # export OVH_APPLICATION_SECRET="YOUR_APP_SECRET"
  # export OVH_CONSUMER_KEY="YOUR_CONSUMER_KEY"

  endpoint = "ovh-eu" # Or "ovh-us", "ovh-ca" depending on your region
}

# Define variables for region and other configurable parameters
variable "region" {
  description = "The OVH region to deploy resources in."
  type        = string
  default     = "GRA" # Example: Gravelines, France
}

variable "project_id" {
  description = "Your OVHcloud Project ID."
  type        = string
  # It's recommended to fetch this dynamically or via an environment variable
  # default = "YOUR_PROJECT_ID"
}

# Use a data source to dynamically fetch the project ID if not hardcoded
data "ovh_cloud_project" "my_project" {
  # If you have multiple projects, you might need to filter by name or ID
  # For simplicity, assuming a single project or using environment variable
  # project_id = var.project_id
}

output "project_id" {
  description = "The ID of the OVHcloud project."
  value       = data.ovh_cloud_project.my_project.id
}

Securing the Laravel Cluster: Network and Instance Configuration

A secure Laravel cluster requires a well-defined network topology and hardened instance configurations. We’ll leverage OVH’s Public Cloud capabilities to create a Virtual Private Cloud (VPC), subnets, and security groups. For the application servers, we’ll use Ubuntu LTS images and configure them to run Nginx as a reverse proxy and PHP-FPM for application execution.

VPC and Subnet Definition

# network.tf

resource "ovh_cloud_network_private" "vpc" {
  service_name = data.ovh_cloud_project.my_project.id
  name         = "laravel-vpc"
  region       = var.region
  description  = "VPC for Laravel cluster"
}

resource "ovh_cloud_network_subnet" "app_subnet" {
  service_name = data.ovh_cloud_project.my_project.id
  network_id   = ovh_cloud_network_private.vpc.id
  name         = "app-subnet"
  region       = var.region
  cidr         = "10.0.1.0/24" # Private subnet for application servers
  gateway_ip   = "10.0.1.1"
}

resource "ovh_cloud_network_subnet" "db_subnet" {
  service_name = data.ovh_cloud_project.my_project.id
  network_id   = ovh_cloud_network_private.vpc.id
  name         = "db-subnet"
  region       = var.region
  cidr         = "10.0.2.0/24" # Private subnet for database servers
  gateway_ip   = "10.0.2.1"
}

Security Groups for Network Access Control

Security groups act as virtual firewalls. We’ll define rules to allow only necessary traffic. For the application servers, we’ll allow SSH from a trusted IP range, HTTP/HTTPS from anywhere, and internal communication within the VPC. For the database servers, we’ll restrict access to only the application subnet and potentially SSH from a bastion host or management network.

# security.tf

resource "ovh_compute_security_group" "app_sg" {
  service_name = data.ovh_cloud_project.my_project.id
  name         = "laravel-app-sg"
  region       = var.region
  description  = "Security group for Laravel application servers"
}

resource "ovh_compute_security_group_rule" "app_sg_ssh" {
  service_name    = data.ovh_cloud_project.my_project.id
  security_group  = ovh_compute_security_group.app_sg.name
  region          = var.region
  direction       = "ingress"
  protocol        = "tcp"
  port_min        = 22
  port_max        = 22
  remote_ip_prefix = "YOUR_TRUSTED_SSH_IP/32" # IMPORTANT: Restrict this to your management IP
}

resource "ovh_compute_security_group_rule" "app_sg_http" {
  service_name    = data.ovh_cloud_project.my_project.id
  security_group  = ovh_compute_security_group.app_sg.name
  region          = var.region
  direction       = "ingress"
  protocol        = "tcp"
  port_min        = 80
  port_max        = 80
  remote_ip_prefix = "0.0.0.0/0"
}

resource "ovh_compute_security_group_rule" "app_sg_https" {
  service_name    = data.ovh_cloud_project.my_project.id
  security_group  = ovh_compute_security_group.app_sg.name
  region          = var.region
  direction       = "ingress"
  protocol        = "tcp"
  port_min        = 443
  port_max        = 443
  remote_ip_prefix = "0.0.0.0/0"
}

# Allow internal communication within the VPC
resource "ovh_compute_security_group_rule" "app_sg_internal" {
  service_name    = data.ovh_cloud_project.my_project.id
  security_group  = ovh_compute_security_group.app_sg.name
  region          = var.region
  direction       = "ingress"
  protocol        = "all"
  remote_ip_prefix = "10.0.0.0/16" # Adjust to cover your entire VPC CIDR range
}

resource "ovh_compute_security_group" "db_sg" {
  service_name = data.ovh_cloud_project.my_project.id
  name         = "laravel-db-sg"
  region       = var.region
  description  = "Security group for Laravel database servers"
}

resource "ovh_compute_security_group_rule" "db_sg_mysql" {
  service_name    = data.ovh_cloud_project.my_project.id
  security_group  = ovh_compute_security_group.db_sg.name
  region          = var.region
  direction       = "ingress"
  protocol        = "tcp"
  port_min        = 3306 # MySQL default port
  port_max        = 3306
  remote_ip_prefix = "10.0.1.0/24" # Allow access ONLY from the app subnet
}

resource "ovh_compute_security_group_rule" "db_sg_ssh" {
  service_name    = data.ovh_cloud_project.my_project.id
  security_group  = ovh_compute_security_group.db_sg.name
  region          = var.region
  direction       = "ingress"
  protocol        = "tcp"
  port_min        = 22
  port_max        = 22
  remote_ip_prefix = "YOUR_TRUSTED_SSH_IP/32" # IMPORTANT: Restrict this to your management IP or bastion host
}

Application Server Instances with User Data

We’ll define compute instances for our Laravel application. Each instance will be configured with a user data script that automates the setup of Nginx, PHP-FPM, and the necessary Laravel dependencies. This script is crucial for achieving a consistent and reproducible deployment.

# instances.tf

variable "app_instance_type" {
  description = "Instance type for application servers (e.g., 'vps-ssd-2', 'c2-15') "
  type        = string
  default     = "c2-15" # Example: 2 vCPU, 4 GB RAM
}

variable "app_instance_count" {
  description = "Number of application servers."
  type        = number
  default     = 2
}

variable "db_instance_type" {
  description = "Instance type for database servers."
  type        = string
  default     = "c2-15"
}

variable "db_image_id" {
  description = "Image ID for the database server (e.g., Ubuntu, Debian)."
  type        = string
  default     = "ubuntu-2204" # Example: Ubuntu 22.04 LTS
}

variable "app_image_id" {
  description = "Image ID for the application servers (e.g., Ubuntu, Debian)."
  type        = string
  default     = "ubuntu-2204" # Example: Ubuntu 22.04 LTS
}

resource "ovh_compute_instance" "app_server" {
  count            = var.app_instance_count
  service_name     = data.ovh_cloud_project.my_project.id
  name             = "laravel-app-${count.index}"
  region           = var.region
  flavor_id        = var.app_instance_type
  image_id         = var.app_image_id
  subnet_id        = ovh_cloud_network_subnet.app_subnet.id
  security_groups  = [ovh_compute_security_group.app_sg.name]
  key_name         = "your-ssh-key-name" # IMPORTANT: Replace with your actual SSH key name in OVH
  user_data        = templatefile("${path.module}/scripts/app_setup.sh", {
    db_host = ovh_compute_instance.db_server.private_ip # Assuming single DB server for simplicity
    db_name = "laravel_db"
    db_user = "laravel_user"
    db_pass = "supersecretpassword" # Use secrets management for production!
    app_env = "production"
  })
  depends_on = [
    ovh_cloud_network_subnet.app_subnet,
    ovh_compute_security_group.app_sg
  ]
}

resource "ovh_compute_instance" "db_server" {
  service_name     = data.ovh_cloud_project.my_project.id
  name             = "laravel-db-01"
  region           = var.region
  flavor_id        = var.db_instance_type
  image_id         = var.db_image_id
  subnet_id        = ovh_cloud_network_subnet.db_subnet.id
  security_groups  = [ovh_compute_security_group.db_sg.name]
  key_name         = "your-ssh-key-name" # IMPORTANT: Replace with your actual SSH key name in OVH
  user_data        = templatefile("${path.module}/scripts/db_setup.sh", {
    db_root_pass = "superrootpassword" # Use secrets management for production!
    db_name      = "laravel_db"
    db_user      = "laravel_user"
    db_pass      = "supersecretpassword" # Use secrets management for production!
  })
  depends_on = [
    ovh_cloud_network_subnet.db_subnet,
    ovh_compute_security_group.db_sg
  ]
}

# Output private IPs for internal access
output "app_server_private_ips" {
  description = "Private IP addresses of the application servers."
  value       = [for instance in ovh_compute_instance.app_server : instance.private_ip]
}

output "db_server_private_ip" {
  description = "Private IP address of the database server."
  value       = ovh_compute_instance.db_server.private_ip
}

`scripts/app_setup.sh` – Application Server Bootstrap Script

#!/bin/bash
set -euxo pipefail

# Update package lists and install essential packages
sudo apt-get update -y
sudo apt-get install -y \
    nginx \
    php-fpm \
    php-mysql \
    php-mbstring \
    php-xml \
    php-curl \
    php-zip \
    git \
    unzip \
    software-properties-common \
    ca-certificates

# Configure PHP-FPM
sudo sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/' /etc/php/8.1/fpm/php.ini # Adjust PHP version if needed
sudo sed -i 's/user = www-data/user = ubuntu/' /etc/php/8.1/fpm/pool.d/www.conf # Match user to system user
sudo sed -i 's/group = www-data/group = ubuntu/' /etc/php/8.1/fpm/pool.d/www.conf
sudo systemctl restart php8.1-fpm # Adjust PHP version if needed

# Configure Nginx
sudo cat > /etc/nginx/sites-available/laravel << EOF
server {
    listen 80;
    server_name _; # Listen on all hostnames

    root /var/www/html/public; # Assuming Laravel project will be deployed here
    index index.php index.html index.htm;

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        # Adjust socket path based on PHP-FPM 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 .env files and other sensitive files
    location ~ /\.env { deny all; }
    location ~ /\.git { deny all; }
    location ~ /\.env\.example { deny all; }
    location ~ /\.env\.testing { deny all; }
    location ~ /\.env\.local { deny all; }
    location ~ /\.env\.prod { deny all; }
    location ~ /\.env\.production { deny all; }
    location ~ /\.env\.staging { deny all; }
    location ~ /\.env\.dev { deny all; }
    location ~ /\.env\.development { deny all; }
    location ~ /\.env\.ci { deny all; }
    location ~ /\.env\.travis { deny all; }
    location ~ /\.env\.circle { deny all; }
    location ~ /\.env\.gitlab-ci { deny all; }
    location ~ /\.env\.github { deny all; }
    location ~ /\.env\.local\.php { deny all; }
    location ~ /\.env\.php { deny all; }
    location ~ /\.env\.yaml { deny all; }
    location ~ /\.env\.yml { deny all; }
    location ~ /\.env\.json { deny all; }
    location ~ /\.env\.ini { deny all; }
    location ~ /\.env\.conf { deny all; }
    location ~ /\.env\.config { deny all; }
    location ~ /\.env\.dist { deny all; }
    location ~ /\.env\.example\.php { deny all; }
    location ~ /\.env\.example\.yaml { deny all; }
    location ~ /\.env\.example\.yml { deny all; }
    location ~ /\.env\.example\.json { deny all; }
    location ~ /\.env\.example\.ini { deny all; }
    location ~ /\.env\.example\.conf { deny all; }
    location ~ /\.env\.example\.config { deny all; }
    location ~ /\.env\.example\.dist { deny all; }
    location ~ /\.env\.example\.local { deny all; }
    location ~ /\.env\.example\.prod { deny all; }
    location ~ /\.env\.example\.production { deny all; }
    location ~ /\.env\.example\.staging { deny all; }
    location ~ /\.env\.example\.dev { deny all; }
    location ~ /\.env\.example\.development { deny all; }
    location ~ /\.env\.example\.ci { deny all; }
    location ~ /\.env\.example\.travis { deny all; }
    location ~ /\.env\.example\.circle { deny all; }
    location ~ /\.env\.example\.gitlab-ci { deny all; }
    location ~ /\.env\.example\.github { deny all; }
    location ~ /\.env\.example\.local\.php { deny all; }
    location ~ /\.env\.example\.php { deny all; }
    location ~ /\.env\.example\.yaml { deny all; }
    location ~ /\.env\.example\.yml { deny all; }
    location ~ /\.env\.example\.json { deny all; }
    location ~ /\.env\.example\.ini { deny all; }
    location ~ /\.env\.example\.conf { deny all; }
    location ~ /\.env\.example\.config { deny all; }
    location ~ /\.env\.example\.dist { deny all; }

    # Prevent access to hidden files
    location ~ /\. { deny all; }
}
EOF

sudo ln -sf /etc/nginx/sites-available/laravel /etc/nginx/sites-enabled/laravel
sudo rm -f /etc/nginx/sites-enabled/default # Remove default Nginx site

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

# Create application directory and set permissions
sudo mkdir -p /var/www/html
sudo chown -R ubuntu:ubuntu /var/www/html

# Deploy Laravel application (example using git clone)
# In a real-world scenario, you'd use a CI/CD pipeline for deployment
# and manage application code separately.
# For this example, we'll clone a placeholder.
# git clone YOUR_LARAVEL_REPO /var/www/html
# cd /var/www/html
# composer install --no-dev --optimize-autoloader
# cp .env.example .env
# sed -i "s/DB_HOST=.*/DB_HOST=${DB_HOST}/" .env
# sed -i "s/DB_DATABASE=.*/DB_DATABASE=${DB_NAME}/" .env
# sed -i "s/DB_USERNAME=.*/DB_USERNAME=${DB_USER}/" .env
# sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${DB_PASS}/" .env
# php artisan key:generate --force
# php artisan config:cache
# php artisan route:cache
# php artisan view:cache
# php artisan migrate --force

# Placeholder for application deployment
echo "Placeholder for Laravel application deployment." >> /var/www/html/index.html
echo "

Welcome to your Laravel Cluster!

" >> /var/www/html/index.html echo "<p>Database Host: ${db_host}</p>" >> /var/www/html/index.html echo "<p>App Environment: ${app_env}</p>" >> /var/www/html/index.html # Ensure correct ownership for web server access if needed (e.g., for storage/cache) # sudo chown -R www-data:www-data /var/www/html/storage # sudo chown -R www-data:www-data /var/www/html/bootstrap/cache # Install and configure firewall (ufw) sudo apt-get install -y ufw sudo ufw allow ssh sudo ufw allow 'Nginx Full' sudo ufw allow from 10.0.0.0/16 to any port 3306 # Allow DB access from VPC sudo ufw --force enable echo "Application server setup complete."

`scripts/db_setup.sh` – Database Server Bootstrap Script

#!/bin/bash
set -euxo pipefail

# Install MySQL Server
sudo apt-get update -y
sudo apt-get install -y mysql-server

# Secure MySQL installation
# This is a non-interactive way to set the root password and remove insecure defaults.
# For production, consider more robust methods like pre-seeding or configuration management tools.
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password ${db_root_pass}"
sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password ${db_root_pass}"
sudo apt-get install -y mysql-server

# Start MySQL service
sudo systemctl start mysql
sudo systemctl enable mysql

# Create database and user
sudo mysql -u root -p"${db_root_pass}" <<MYSQL_SCRIPT
CREATE DATABASE IF NOT EXISTS ${db_name};
CREATE USER IF NOT EXISTS '${db_user}'@'%' IDENTIFIED BY '${db_pass}';
GRANT ALL PRIVILEGES ON ${db_name}.* TO '${db_user}'@'%';
FLUSH PRIVILEGES;
MYSQL_SCRIPT

# Configure MySQL for remote access (if needed, but better to restrict via SG)
# For security, we'll rely on the security group to control access.
# If you need to bind to a specific IP, edit /etc/mysql/mysql.conf.d/mysqld.cnf
# and restart mysql service.

# Install and configure firewall (ufw)
sudo apt-get install -y ufw
sudo ufw allow ssh
sudo ufw allow mysql # Allow MySQL port (3306)
# IMPORTANT: The security group already restricts access to 10.0.1.0/24.
# If you were to use UFW for external access control, you'd add rules here.
# For internal VPC access, SG is primary.
sudo ufw --force enable

echo "Database server setup complete."

Load Balancing and SSL Termination

For high availability and scalability, a load balancer is essential. OVHcloud offers Load Balancer services that can distribute traffic across your application servers. We'll configure an HTTP(S) load balancer with SSL termination to handle incoming web traffic securely.

OVH Load Balancer Configuration

# loadbalancer.tf

resource "ovh_loadbalancer" "laravel_lb" {
  service_name = data.ovh_cloud_project.my_project.id
  name         = "laravel-lb"
  region       = var.region
  description  = "Load balancer for Laravel cluster"
  type         = "public" # Or "internal" if only for internal VPC traffic
}

# Frontend configuration for HTTP
resource "ovh_loadbalancer_frontend" "http_frontend" {
  loadbalancer_id = ovh_loadbalancer.laravel_lb.id
  name            = "http"
  port            = 80
  protocol        = "http"
}

# Frontend configuration for HTTPS
resource "ovh_loadbalancer_frontend" "https_frontend" {
  loadbalancer_id = ovh_loadbalancer.laravel_lb.id
  name            = "https"
  port            = 443
  protocol        = "https"
  ssl_certificate = file("path/to/your/certificate.pem") # Path to your SSL certificate
  ssl_private_key = file("path/to/your/private_key.pem") # Path to your SSL private key
  # For production, use OVH's SSL certificate management or a dedicated secrets manager
}

# Backend pool for application servers
resource "ovh_loadbalancer_backend" "app_backend" {
  loadbalancer_id = ovh_loadbalancer.laravel_lb.id
  name            = "app-pool"
  port            = 80 # Application servers listen on port 80
  protocol        = "http"
  method          = "roundrobin" # Or "leastconn", "source"
  fallback_port   = 80
  fallback_protocol = "http"

  # Health check configuration
  health_check {
    port     = 80
    protocol = "http"
    uri      = "/" # Adjust to a health check endpoint in your Laravel app if available
    interval = 5 # seconds
    timeout  = 2 # seconds
    method   = "GET"
  }

  # Attach application servers to the backend pool
  dynamic "servers" {
    for_each = ovh_compute_instance.app_server
    content {
      address = servers.value.private_ip
      status  = "active"
    }
  }
}

# Associate frontends with backends
resource "ovh_loadbalancer_route" "http_to_app" {
  loadbalancer_id = ovh_loadbalancer.laravel_lb.id
  frontend_id     = ovh_loadbalancer_frontend.http_frontend.id
  backend_id      = ovh_loadbalancer_backend.app_backend.id
}

resource "ovh_loadbalancer_route" "https_to_app" {
  loadbalancer_id = ovh_loadbalancer.laravel_lb.id
  frontend_id     = ovh_loadbalancer_frontend.https_frontend.id
  backend_id      = ovh_loadbalancer_backend.app_backend.id
}

# Output the public IP of the load balancer
output "loadbalancer_public_ip" {
  description = "Public IP address of the OVH Load Balancer."
  value       = ovh_loadbalancer.laravel_lb.public_ip
}

Deployment Workflow and Considerations

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

  • Initialize Terraform: Run terraform init to download the OVH provider and any other necessary plugins.
  • Review Plan: Execute terraform plan to see the resources that will be created, modified, or destroyed. Carefully review this output.
  • Apply Configuration: Run terraform apply to provision the infrastructure on OVHcloud. Confirm with 'yes' when prompted.
  • Deploy Application Code: After the infrastructure is up, you'll need to deploy your Laravel application code. This can be done manually via SSH, or preferably, through a CI/CD pipeline (e.g., GitLab CI, GitHub Actions, Jenkins) that clones the repository, runs composer install, sets up the .env file, and performs migrations.
  • Configure DNS: Update your domain's DNS records to point to the public IP address of the OVH Load Balancer.

Security Best Practices and Enhancements

To further enhance the security of your Laravel cluster:

  • Secrets Management: Never hardcode sensitive information like database passwords or API keys. Use environment variables, HashiCorp Vault, AWS Secrets Manager, or OVH's secrets management solutions. The provided scripts use template variables for demonstration, which should be replaced with a secure mechanism.
  • SSH Key Management: Ensure your SSH keys are strong and managed securely. Restrict SSH access to only necessary IP addresses in your security groups.
  • Regular Updates: Keep your operating systems, Nginx, PHP, and Laravel dependencies updated with the latest security patches. Automate this process using tools like unattended-upgrades.
  • Web Application Firewall (WAF): Consider deploying a WAF (e.g., ModSecurity with Nginx, or a cloud-based WAF service) to protect against common web exploits like SQL injection and XSS.
  • Database Backups: Implement a robust backup strategy for your database, storing backups off-site and testing restoration regularly.
  • Monitoring and Logging: Set up comprehensive monitoring and logging for your application servers, database, and load balancer. Centralize logs for easier analysis and incident response.
  • Immutable Infrastructure: For even greater consistency and security, consider an immutable infrastructure approach where servers are replaced rather than updated in place. This can be achieved by baking application code into custom images or using containerization (Docker) with orchestration (Kubernetes).

By combining Terraform for infrastructure provisioning with secure configuration scripts and robust security practices, you can establish a highly available and secure Laravel cluster on OVHcloud.

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