WordPress Deployment on OpenStack Flex

Quickly and easily deploying WordPress on Rackspace OpenStack Flex

Heat Orchestration Template (HOT) deploys a fully functional WordPress site on an Ubuntu VM in Rackspace OpenStack Flex. This template uses the practice of "infrastructure as code" and automates all the prerequisites needed for a working OpenStack environment. This means that it does the work of network stack provisioning - but it also handles server configuration, WordPress installation, and HTTPS certificate setup using Let's Encrypt.

If you're interested in learning how to use OpenStack Orchestration, see our guide here

Features

  • Creates private network, subnet, router, and security group
  • Launches a single Ubuntu-based VM
  • Installs Apache, PHP, MySQL, and WordPress
  • Automatically installs a free HTTPS certificate via Let's Encrypt
  • Continues checking for 1.5hrs for DNS to resolve the domain correctly so that it can install the certificate
  • Opens HTTP (80) and HTTPS (443) to the world

Requirements

Before launching the stack:

Template

heat_template_version: '2018-03-02'
description: >
  HOT template to deploy a WordPress server with automatic HTTPS using Certbot,
  installed via a systemd service that retries DNS resolution post-boot.

parameters:
  private_net_cidr:
    type: string
    default: 10.0.0.0/24
  private_net_gateway:
    type: string
    default: 10.0.0.1
  image:
    type: string
    default: ubuntu-20.04
  flavor:
    type: string
    default: m1.small
  public_net:
    type: string
    description: ID of the public network
  key_name:
    type: string
    description: Name of the key pair to use for the VM instance
  site_domain:
    type: string
    description: The full domain name to be used (e.g. wpsite.example.com)
  admin_email:
    type: string
    description: Email address for Let's Encrypt registration

resources:
  private_network:
    type: OS::Neutron::Net
  private_subnet:
    type: OS::Neutron::Subnet
    properties:
      network_id: { get_resource: private_network }
      cidr: { get_param: private_net_cidr }
      gateway_ip: { get_param: private_net_gateway }
      dns_nameservers: [8.8.8.8, 8.8.4.4]
  router:
    type: OS::Neutron::Router
    properties:
      external_gateway_info:
        network: { get_param: public_net }
  router_interface:
    type: OS::Neutron::RouterInterface
    properties:
      router_id: { get_resource: router }
      subnet_id: { get_resource: private_subnet }
  vm_security_group:
    type: OS::Neutron::SecurityGroup
    properties:
      rules:
        - { protocol: tcp, port_range_min: 22, port_range_max: 22, remote_ip_prefix: 0.0.0.0/0 }
        - { protocol: tcp, port_range_min: 80, port_range_max: 80, remote_ip_prefix: 0.0.0.0/0 }
        - { protocol: tcp, port_range_min: 443, port_range_max: 443, remote_ip_prefix: 0.0.0.0/0 }
        - { protocol: icmp, remote_ip_prefix: 0.0.0.0/0 }
  vm_port:
    type: OS::Neutron::Port
    properties:
      network_id: { get_resource: private_network }
      security_groups: [ { get_resource: vm_security_group } ]
  floating_ip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network_id: { get_param: public_net }
      port_id: { get_resource: vm_port }
  wordpress_server:
    type: OS::Nova::Server
    properties:
      name: wordpress_server
      image: { get_param: image }
      flavor: { get_param: flavor }
      key_name: { get_param: key_name }
      networks:
        - port: { get_resource: vm_port }
      user_data_format: RAW
      user_data:
        str_replace:
          params:
            __DOMAIN__: { get_param: site_domain }
            __EMAIL__: { get_param: admin_email }
          template: |
            #!/bin/bash
            apt-get update
            apt-get install -y apache2 mysql-server php php-mysql libapache2-mod-php certbot python3-certbot-apache

            mysql -e "CREATE DATABASE wordpress;"
            mysql -e "CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'password';"
            mysql -e "GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';"
            mysql -e "FLUSH PRIVILEGES;"

            cd /var/www/html
            wget https://wordpress.org/latest.tar.gz
            tar -xvzf latest.tar.gz
            rm latest.tar.gz
            mv wordpress/* .
            rmdir wordpress
            cp wp-config-sample.php wp-config.php
            sed -i "s/database_name_here/wordpress/" wp-config.php
            sed -i "s/username_here/wpuser/" wp-config.php
            sed -i "s/password_here/password/" wp-config.php
            chown -R www-data:www-data /var/www/html
            chmod -R 755 /var/www/html

            cat <<EOF > /usr/local/bin/wait-for-dns-certbot.sh
            #!/bin/bash
            IP=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4)
            for i in {1..180}; do
              RESOLVED=$(getent ahosts __DOMAIN__ | awk '{print $1; exit}')
              if [ "$RESOLVED" == "$IP" ]; then
                certbot --apache --non-interactive --agree-tos --email __EMAIL__ -d __DOMAIN__
                systemctl reload apache2
                exit 0
              fi
              sleep 30
            done
            echo "DNS failed to resolve to correct IP after waiting."
            EOF

            chmod +x /usr/local/bin/wait-for-dns-certbot.sh
            cat <<EOF > /etc/systemd/system/certbot-wait.service
            [Unit]
            Description=Wait for DNS then run Certbot
            After=network.target
            [Service]
            ExecStart=/usr/local/bin/wait-for-dns-certbot.sh
            Type=oneshot
            RemainAfterExit=true
            [Install]
            WantedBy=multi-user.target
            EOF

            systemctl daemon-reexec
            systemctl daemon-reload
            systemctl enable certbot-wait.service
            systemctl start certbot-wait.service

            systemctl restart apache2

outputs:
  instance_ip:
    description: IP address of the WordPress server
    value: { get_attr: [ floating_ip, floating_ip_address ] }

Stack Parameters

ParameterDescriptionDefault Value
private_net_cidrCIDR block for internal network10.0.0.0/24
private_net_gatewayGateway IP for subnet10.0.0.1
imageImage to use for the VM (must be Ubuntu 20.04+)Ubuntu 24.04
flavorFlavor (hardware profile) of the VMgp.5.2.4
public_netID or name of the public networkPUBLICNET
key_nameSSH key name registered in OpenStack(required)
site_domainDomain name for the WordPress site (e.g., blog.example.com)(required)
admin_emailThe email address for the Let's Encrypt registration(required)

Note: The flavor can be changed to something more powerful if needed. See our guide on flavors here.

Accessing Your WordPress Site

Once the stack is deployed and DNS is configured:

  • Visit https://yourdomain.com/wp-admin to finish WordPress setup.
  • If HTTPS isn’t working initially, verify DNS is resolving before stack creation.

Security Notes

  • SSH (port 22), HTTP (port 80), and HTTPS (port 443) are open to the internet.
  • You may want to restrict SSH access further in a production environment.

Troubleshooting

  • DNS should be updated soon after launching the stack, Certbot will fail.
  • If your DNS is updated but the site is not accessible via https:// yet - give it at least 5-10 minutes to perform the automated checks and installations.
  • Redeploy the stack only after fixing DNS.
  • Certbot will not retry unless the server is rebooted.