Hey everyone!
In the daily life of a PHP developer, it's very common to need a quick environment for testing, prototyping, or even to host a low-traffic application. And, often, we want something that is easy to configure, without the complexity of SSH Keys or advanced configurations right off the bat.
With that in mind, Azure emerges as a powerful and accessible tool to create virtual machines in minutes!
In this post, we will demystify the process of creating a Virtual Machine in Azure with Debian 12 (Bookworm), the version available in Azure images, configuring a complete environment with Apache, PHP 8.5, and the drivers for connecting to SQL Server.
In addition, we will optimize performance and security with OPcache, UFW, fail2ban, and Cloudflare tips, including how to enable HTML page caching.
Get ready to have your web environment running in minutes, with everything you need to connect your PHP applications to SQL Server or MySQL/MariaDB, and with that touch of performance that makes all the difference. So, let's go!
Creating the demonstration base in Azure
Creating a free Azure account
If you don't have an Azure account yet, great news: Microsoft offers a free account with USD 200 in credits to use in the first 30 days, plus more than 55 free services for 12 months, more than enough for this tutorial.
To create your free account:
- Go to azure.microsoft.com/free and click on "Try for free".
- Sign in with your Microsoft account (Outlook, Hotmail, Xbox) — or click on "Create one" if you don't have one.
- Fill in your personal details: full name, country, and date of birth.
- Provide a phone number to receive the verification code.
- Provide a credit or debit card. It is only required to verify that you are a real person and does not generate automatic charges.
- Accept the terms and click on "Sign up". In a few seconds, you will have access to the portal.
The card is used only for identity verification. You will not be charged while you are within the limits of the free account and do not manually upgrade to a paid subscription. After the first 30 days, the USD 200 credits expire, but the services marked as "free for 12 months" remain available at no cost.
Creating the Virtual Machine in Azure
With your account created, access the portal.azure.com and follow the steps below to create your virtual machine:
- In the search bar, type "Virtual Machines" and select the option.
- Click on "Create" and then on "Azure Virtual Machine".
- Fill in the basic information:
- Subscription: Choose your subscription.
- Resource group: Create a new one (e.g.,
rg-webserver) or use an existing one. - Virtual machine name: A meaningful name (e.g.,
vm-debian-web). - Region: Choose the region closest to you or your users.
- Availability options: Leave as "No infrastructure redundancy required".
- Security type: Leave "Standard".
- Image: Search and select "Debian 12 (Bookworm)".
- Size: Choose a size that meets your needs. For testing, a
Standard_B1s(1 vCPU, 1 GB RAM) is sufficient for small projects.
- Under "Administrator account", here's the trick for those who don't want complications with SSH Keys:
- Authentication type: Select "Password".
- Username: Create a username (e.g.,
dirceu). - Password: Create a strong password and write it down, as you will need it to access the VM.
- Under "Public inbound ports rules", select "Allow selected ports" and choose "HTTP (80)", "HTTPS (443)", and "SSH (22)". The SSH port is necessary to access the VM via terminal.
- Click on "Review + create" and, after validation, on "Create".

Wait a few minutes while Azure provisions your virtual machine. Once it's ready, you'll have a public IP to access it. Write down this IP!
Configuring a static public IP
By default, Azure assigns a dynamic public IP to the VM, which means it changes every time the VM is restarted. When this happens, you lose SSH access, Cloudflare DNS points to the wrong IP, and you need to update everything again. Therefore, the first step is to make the IP static.
- On your VM's page in Azure, click on the name of the public IP that appears in the overview pane (something like
vm-debian-web-ip). - In the side menu, click on "Configuration".
- Under "Assignment", change from "Dynamic" to "Static".
- Click on "Save".


The IP that appears on the screen is now permanent and will not change even if you restart, shut down, or resize the VM. Write down this IP, as you will use it in the NSG, Cloudflare DNS, and FileZilla/PuTTY settings.
Allowing SSH access in the NSG (Network Security Group)
Before connecting to the VM, it's worth checking if the NSG (Network Security Group), Azure's network firewall, is allowing SSH connections from your IP. The NSG is created along with the VM, but the SSH rule might be open to any IP in the world, which is a security risk, or it might not even exist.
It is recommended to restrict SSH only to your IP. Here's how:
1. Find your current public IP
Before messing with the NSG, you need to know your current public IP, which is the IP that Azure will "see" when you try to connect. Access one of the sites below and write down the IPv4:
If your internet provider uses a dynamic IP (which is the case for most residential connections), your IP may change periodically. Whenever you lose SSH access, check if your IP has changed and update the rule in the NSG.
2. Access the NSG in the Azure portal
- On the portal.azure.com, access your VM and, in the left side menu, click on "Networking".
- Click on the NSG name displayed next to "Network security group" — usually something like
vm-debian-web-nsg. - In the NSG's side menu, click on "Inbound security rules".
3. Edit (or create) the SSH rule
Look for an existing rule with destination port 22 and protocol TCP. If it exists and has source Any or *, click on it to edit. If it doesn't exist, click on "+ Add" to create a new one.
Fill in the fields:
- Source: select "IP Addresses"
- Source IP addresses / CIDR ranges: paste your public IP followed by
/32(e.g.,177.90.45.123/32). The/32means "only this specific IP". - Destination port ranges:
22 - Protocol: TCP
- Action: Allow
- Priority: a low number (e.g.,
300) ensures this rule is evaluated before more generic ones. - Name: something descriptive, like
Allow-SSH-MyIP.
Click on "Save". The rule takes effect in a few seconds.



If your company or school uses a corporate network with NAT, you may need to allow an IP range instead of a single address. In this case, consult your network administrator for the correct CIDR block (e.g., 200.155.10.0/24).
Never leave SSH open to the world
Avoid keeping the SSH rule with source Any (0.0.0.0/0). Servers with SSH exposed to the internet receive automated brute-force attempts within minutes of creation. Restricting to your IP virtually eliminates 100% of these attempts.
Accessing the VM via SSH
With the VM created and the public IP in hand (you can find it on the VM details page in Azure, under "Public IP"), it's time to connect to it. The connection is via SSH, a protocol that encrypts all communication with the server. The options depend on your operating system:
Option 1: PuTTY (recommended for beginners on Windows)
PuTTY is a free SSH client with a graphical interface, very popular among Windows users. To install and connect:
- Go to chiark.greenend.org.uk/~sgtatham/putty/latest.html and download the installer
putty-64bit-X.XX-installer.msi. - Run the installer and click "Next" until finished.
- Open PuTTY. In the "Host Name (or IP address)" field, paste the public IP of your VM in Azure.
- Confirm that the port is 22 and the connection type is SSH.
- Tip: in the "Saved Sessions" field, give the connection a name (e.g.,
vm-azure) and click "Save" — this way you don't have to type the IP every time. - Click "Open".
- On the first connection, PuTTY displays a warning about the server's key — click "Accept". This is normal and expected.
- In the black window that opens, type your username (the one you created in Azure) and press Enter.
- Type your password — the characters do not appear on the screen as you type, this is intentional — and press Enter.
You are inside the VM! The terminal will display something like your_user@vm-debian-web:~$, confirming the successful connection.
If you use mRemoteNG, the connection screen will look like this:

Option 2: Native Terminal (Linux, macOS, or Windows 10+)
On Linux, macOS, and Windows 10/11 (PowerShell or Windows Terminal), SSH is already available without needing to install anything. Open the terminal and execute:
ssh seu_usuario@IP_PUBLICO_DA_VM
On the first connection, confirm with yes when asked about the host's authenticity. Then, enter your password. Once connected, let's get started!
Preparing the system
With the connection established, elevate privileges to the root user to facilitate installations:
sudo su
Next, update the repositories to ensure we are installing the latest versions of the packages:
apt-get update
Result: Your environment is ready to receive the software we need.
Installing and Configuring Apache
Now we will install the Apache web server and some important dependencies for PHP and other functionalities.
Use the script below:
# Install Apache and dependencies
apt-get --yes install build-essential autoconf flex bison
apt-get --yes install apache2 apache2-dev libapache2-mod-evasive apache2-utils
apt-get --yes install libpng-dev
apt-get --yes install zlib1g zlib1g-dev libxml2 libxml2-dev
apt-get --yes install openssl libssl-dev
apt-get --yes install libgd-dev
apt-get --yes install vim curl libcurl4 libcurl4-openssl-dev
apt-get --yes install libfreetype6-dev libreadline-dev sqlite3
apt-get --yes install rpl zip libzip-dev libbz2-dev unzip libldap2-dev pwgen
apt-get --yes install unixodbc unixodbc-dev s3cmd
Packages removed in Debian 12
The packages libssl1.1, libmcrypt-dev, libmcrypt4, mcrypt, libgdchart-gd2-xpm and libaio1 do not exist in Debian 12 (Bookworm) and will cause an installation error. OpenSSL 3.x is already covered by libssl-dev included in the list above.
Result: Apache and its essential dependencies are installed. You can now access your VM's public IP in the browser and you will see the default Apache page ("Apache2 Debian Default Page").
Enabling .htaccess and Modules
For Apache to work correctly with PHP applications like WordPress, we need to enable the use of .htaccess files and some important modules.
To enable .htaccess and increase the request line limit:
# Enable htaccess and symbolic links
rpl -e "\n\tOptions Indexes FollowSymLinks\n\tAllowOverride None" \
"\n\tOptions FollowSymLinks\n\tAllowOverride All" \
/etc/apache2/apache2.conf
echo "LimitRequestLine 100000" >> /etc/apache2/apache2.conf
The rpl command is a tool for text replacement in files, similar to sed, but with a more user-friendly syntax. It replaces the default Apache configuration to allow AllowOverride All, which enables .htaccess.
Now, let's enable the Apache modules that are crucial for performance and URL rewriting:
# Enable Apache modules
a2dismod mpm_event
a2dismod mpm_worker
a2enmod mpm_prefork
a2enmod rewrite
a2enmod headers
Result: Apache is configured to use the mpm_prefork module (necessary to work with mod_php), and the rewrite and headers modules are active, allowing friendly URLs and HTTP header manipulation.
Installing and Configuring PHP 8.5
It's time to install PHP! We will use version 8.5, released in November 2025, which is the latest and brings performance improvements and new features compared to previous versions. To do this, we will add Ondřej Surý's package repository, who maintains updated PHP versions for Debian/Ubuntu.
First, add the GPG key and the repository:
# Add PHP repository (Ondřej Surý) — modern method without apt-key
apt install apt-transport-https lsb-release ca-certificates curl gnupg -y
curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \
| tee /etc/apt/sources.list.d/php.list
apt update
The method with signed-by is the current and recommended way to add external repositories in Debian/Ubuntu. The old apt-key add has been marked as obsolete (deprecated) and will be removed in future APT versions.
Now install PHP 8.5 and the necessary extensions:
# Install PHP 8.5 and extensions
apt install php8.5 php8.5-cli php8.5-{bz2,curl,mbstring,intl,xml,gd,zip,mysql,opcache,readline} -y
apt install php-pear wget git php-cgi php-common php-net-socket -y
In addition to the basic extensions, we have already installed php8.5-opcache. OPcache is a native PHP bytecode accelerator that stores compiled code in memory, eliminating the need to recompile scripts on each request. In real PHP applications, the performance gain is usually between 3x and 5x, and it comes built into PHP at no cost.
With OPcache installed, let's configure it optimally. Run the command below to create or replace the configuration file at once, without needing to open a text editor:
cat > /etc/php/8.5/mods-available/opcache.ini << 'EOF'
zend_extension=opcache.so
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=0
EOF
Now, let's integrate PHP 8.5 with Apache using the mod_php module:
# Install mod_php for Apache
apt install libapache2-mod-php8.5 -y
a2enmod php8.5
# Restart Apache to load the module
systemctl restart apache2
Result: PHP 8.5 is installed and configured to work with Apache. Your PHP applications can now be executed!
To check the PHP version and loaded modules, you can use:
php -v
php -m
Result: You will see PHP 8.5 version and a list of all active modules, including OPcache.
Connecting PHP to SQL Server (ODBC)
This is the icing on the cake for those who work with SQL Server! We will install Microsoft's ODBC drivers so that PHP can communicate with SQL Server, whether it's on Azure, on-premises, or another server.
Attention: correct repository for Debian
Microsoft's ODBC drivers have specific repositories per distribution. Always use the repository for Debian — never Ubuntu's — to avoid package incompatibilities and silent installation failures.
To install the ODBC drivers:
# Add GPG key and Microsoft repository for Debian
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg
curl -s https://packages.microsoft.com/config/debian/12/prod.list \
| tee /etc/apt/sources.list.d/mssql-release.list
apt-get update
# Install ODBC drivers and command-line tools
ACCEPT_EULA=Y apt-get -y install msodbcsql18 mssql-tools18
# Add tools to PATH
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bashrc
source ~/.bashrc
# Install build dependencies for PECL (PHP 8.5 headers + XML extension)
apt install php8.5-dev php8.5-xml -y
# Install PHP extensions for SQL Server via PECL
# PHP_PEAR_PHP_BIN ensures pecl uses PHP 8.5, not the system's default PHP
export PHP_PEAR_PHP_BIN=/usr/bin/php8.5
pecl channel-update pecl.php.net
pecl install sqlsrv
pecl install pdo_sqlsrv
The php8.5-dev package provides the necessary compilation headers for PECL to build native extensions. php8.5-xml is mandatory for PEAR itself to function. The PHP_PEAR_PHP_BIN variable instructs PECL to specifically use PHP 8.5 as the compilation base. Without it, PECL uses the system's default PHP (which may be a different version) and fails with the error "XML Extension not found".
Enabling drivers in PHP.ini
After installing the drivers via PECL, we need to inform PHP that they should be loaded. In Debian with Ondřej Surý's repository, the correct way is to create the files in mods-available and use phpenmod, which enables the extension for all SAPIs (CLI, Apache, FPM) at once.
# Create the extension configuration files
echo "extension=sqlsrv.so" > /etc/php/8.5/mods-available/sqlsrv.ini
echo "extension=pdo_sqlsrv.so" > /etc/php/8.5/mods-available/pdo_sqlsrv.ini
# Enable for all PHP 8.5 SAPIs (creates symlinks in cli/conf.d, apache2/conf.d, etc.)
phpenmod -v 8.5 sqlsrv pdo_sqlsrv
phpenmod is Debian's native tool for activating PHP extensions. It automatically creates symlinks in the conf.d directories of each installed SAPI (CLI, Apache, FPM), without needing to manually discover the path. It is equivalent to a2enmod, but for PHP extensions.
For the changes to take effect, restart Apache:
systemctl restart apache2
Result: Your PHP is now ready to connect to SQL Server! To confirm, create a file /var/www/html/phpinfo.php with the content <?php phpinfo(); ?> and access it in the browser. Check if the sqlsrv, pdo_sqlsrv, and opcache sections appear listed.
Security: After validating the installation, immediately remove the phpinfo.php file from the server. This file exposes sensitive information about the environment (PHP version, modules, configurations, paths) and should not be publicly accessible.
Testing PHP → SQL Server connection
In addition to phpinfo(), it's worth doing a real connection test. Create the file below (replace connection data with yours):
nano /var/www/html/test-sqlsrv.php
<?php
$servidor = 'SEU_SERVIDOR.database.windows.net'; // or SQL Server IP
$usuario = 'seu_login';
$senha = 'SuaSenha@123';
$banco = 'seu_banco';
$conn = sqlsrv_connect($servidor, [
'Database' => $banco,
'UID' => $usuario,
'PWD' => $senha,
'TrustServerCertificate' => true,
]);
if ($conn === false) {
echo '<h2>ERRO na conexão:</h2><pre>';
print_r(sqlsrv_errors());
echo '</pre>';
} else {
echo '<h2>✅ Conexão bem-sucedida!</h2>';
$result = sqlsrv_query($conn, 'SELECT @@VERSION AS versao');
$row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC);
echo '<p>' . htmlspecialchars($row['versao']) . '</p>';
sqlsrv_close($conn);
}
?>
Access http://VM_IP/test-sqlsrv.php in your browser. If the green message ✅ with the SQL Server version appears, the connection is working!
Like phpinfo.php, remove this file immediately after the test. It contains credentials and should not be publicly exposed.
rm /var/www/html/test-sqlsrv.php
Permission Settings and Basic Security
Permissions are crucial for the security and proper functioning of applications. Let's adjust some of them and install useful tools.
# Add your user to the www-data group to manage Apache files
# Replace 'your_user' with the username you created in the VM
usermod -a -G www-data seu_usuario
# Update and clean unnecessary packages
apt-get update
apt-get upgrade -y
apt autoremove -y
# Install useful tools
apt-get install htop ncdu -y
Attention
Remember to replace your_username with the username you created when provisioning the VM in Azure. Adding the user to the www-data group allows you to manage files in the Apache folder without needing to use sudo for every operation. Avoid adding your user to the root group — this represents an unnecessary security risk.
Firewall with UFW
A good security practice is to configure the VM's firewall directly on the operating system, in addition to Azure rules. UFW (Uncomplicated Firewall) makes this task much easier:
# Install and configure UFW
apt-get install ufw -y
# Set default policy: block incoming, allow outgoing
ufw default deny incoming
ufw default allow outgoing
# Release necessary ports
ufw allow ssh # port 22
ufw allow http # port 80
ufw allow https # port 443
# Activate the firewall
ufw enable
# Check status
ufw status verbose
Attention
Make sure to open the SSH port (ufw allow ssh) before enabling UFW. Otherwise, you will lose remote access to the VM.
Brute-force Protection with fail2ban
fail2ban monitors system logs and automatically blocks IPs that make too many unsuccessful login attempts via SSH. It's a good extra layer of defense, especially useful if you need to open SSH to more than one IP (home, work...).
# Install fail2ban
apt-get install fail2ban -y
# Create local configuration file (never edit the original .conf)
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit the local file to adjust SSH settings:
nano /etc/fail2ban/jail.local
Locate the [sshd] section and adjust the values:
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 5
bantime = 3600
findtime = 600
What each directive does:
maxretry = 5: blocks the IP after 5 incorrect login attemptsfindtime = 600: the 5 attempts must occur within 10 minutesbantime = 3600: the IP is blocked for 1 hour (use-1for permanent blocking)
# Enable and start the service
systemctl enable fail2ban
systemctl start fail2ban
# Check status and banned IPs
fail2ban-client status sshd
If you yourself enter the wrong password more than 5 times in a row, you may ban your own IP. In this case, use the Azure Serial Console (available in the portal, under Support + Troubleshooting → Serial Console) to access the VM without SSH and run fail2ban-client set sshd unbanip YOUR_IP.
SSL/HTTPS Certificate with Certbot
To have a secure website with HTTPS, we will install Certbot, which automates obtaining and renewing free SSL certificates from Let's Encrypt.
# Install Certbot for Apache
apt install certbot python3-certbot-apache -y
# Get certificate (replace with your real domain)
certbot --apache -d seudominio.com.br -d www.seudominio.com.br
Result: Your site can now be configured to use HTTPS, ensuring secure communication with your users. Certbot also configures automatic certificate renewal.
Configuring VirtualHost for your domain
Until now, all content is located within /var/www/html/. This works for a single site, but Apache has a better way to organize this: VirtualHost, which associates a domain with a specific folder. Certbot also needs a VirtualHost configured to generate the SSL certificate correctly.
# Create the website folder (replace 'yourdomain.com.br' with your domain)
mkdir -p /var/www/seudominio.com.br/public
chown -R seu_usuario:www-data /var/www/seudominio.com.br
# Create the VirtualHost configuration file
nano /etc/apache2/sites-available/seudominio.com.br.conf
Fill the file with the content below (replace the domain and email):
<VirtualHost *:80>
ServerName seudominio.com.br
ServerAlias www.seudominio.com.br
ServerAdmin [email protected]
DocumentRoot /var/www/seudominio.com.br/public
<Directory /var/www/seudominio.com.br/public>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/seudominio-error.log
CustomLog ${APACHE_LOG_DIR}/seudominio-access.log combined
</VirtualHost>
Save and exit: Ctrl+O → Enter → Ctrl+X. Now, activate the VirtualHost and reload Apache:
# Enable the new VirtualHost
a2ensite seudominio.com.br.conf
# Test the configuration before reloading
apache2ctl configtest
# Reload (without dropping active connections)
systemctl reload apache2
Result: Apache now serves the content from /var/www/yourdomain.com.br/public/ when someone accesses http://yourdomain.com.br. Place your PHP files in this folder (via FileZilla) and Certbot will automatically use this VirtualHost when generating the SSL certificate.
Optional: Installing MariaDB and WordPress
If you need a local database or want to test WordPress, follow these steps. Remember that you can use a managed database service in Azure (like Azure Database for MySQL) or an SQL Server elsewhere.
Installing MariaDB
# Install MariaDB server and client
apt-get install mariadb-server mariadb-client -y
# Start and enable the service
systemctl start mariadb
systemctl enable mariadb
# Perform initial secure configuration
mysql_secure_installation
During mysql_secure_installation, you will be guided to set a password for the MariaDB root user, remove anonymous users, disable remote root login, and remove the test database. Follow the recommended instructions.
To create a database and user for WordPress (or your application), access MariaDB and execute the commands below:
mysql -u root -p
-- Create database
CREATE DATABASE wordpressdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Create user (correct syntax for MariaDB 10.4+)
CREATE USER 'wordpressuser'@'localhost' IDENTIFIED BY 'SuaSenhaForte@2024';
GRANT ALL PRIVILEGES ON wordpressdb.* TO 'wordpressuser'@'localhost';
FLUSH PRIVILEGES;
QUIT;
Attention
The GRANT ... IDENTIFIED BY syntax was removed in MariaDB 10.4+. Always create the user with CREATE USER separately before executing GRANT. Also, replace YourStrongPassword@2024 with a unique and secure password — never use generic passwords in production.
Result: You have a running MariaDB server and a database ready for your application.
Installing WordPress
# Download and extract WordPress
cd /var/www/html
wget https://wordpress.org/latest.zip
unzip latest.zip
mv wordpress/* .
rm latest.zip
rmdir wordpress
# Create the configuration file
cp wp-config-sample.php wp-config.php
nano wp-config.php
In the nano editor, edit the wp-config.php file and fill in the database information you created:
define( 'DB_NAME', 'wordpressdb' );
define( 'DB_USER', 'wordpressuser' );
define( 'DB_PASSWORD', 'SuaSenhaForte@2024' );
define( 'DB_HOST', 'localhost' );
define( 'DB_CHARSET', 'utf8mb4' );
Save and exit: Ctrl+O → Enter → Ctrl+X.
Adjust permissions for WordPress to work correctly:
cd /var/www/html
# Add current user to www-data group
sudo usermod -aG www-data seu_usuario
newgrp www-data
# Set file ownership
chown -R www-data:www-data .
# Correct permissions: 775 for directories, 664 for files
find . -type d -exec chmod 775 {} \;
find . -type f -exec chmod 664 {} \;
# Remove default Apache index
rm -f index.html
Result: WordPress is installed and ready to be configured by accessing the public IP (or domain) of your VM in the browser.
Managing Costs and the VM in Azure
How much does it cost to maintain the VM?
After the USD 200 free credits expire (or 30 days), you start paying for actual usage. The prices below are approximate for the Brazil South region (may vary slightly):
Standard_B1ms(1 vCPU, 2 GB RAM): ~USD 15/month if kept on 24h/dayStandard_B2s(2 vCPU, 4 GB RAM): ~USD 35/month if kept on 24h/day- Premium SSD 30 GB disk: ~USD 5/month, charged even with the VM off
- Static public IP: charged only while the VM is off (~USD 0.004/hour)
For testing and learning purposes, the B1ms is more than sufficient. For production with real traffic, consider at least a B2s. Consult the Azure Pricing Calculator to simulate costs for your exact scenario.
How to shut down and pause the VM (and save money)
For test environments that you only use during the day or at specific times, shutting down the VM when not in use can reduce computing costs by up to 70%.
Attention: shutting down via the OS is not enough
If you run shutdown -h now inside the VM or close PuTTY, the VM enters a "Stopped" state, but Azure continues to charge for CPU and RAM. To stop charging for compute, you need to deallocate the VM through the Azure portal.
To shut down and deallocate the VM correctly (stopping CPU/RAM charges):
- In the Azure portal, access your VM.
- Click the "Stop" button in the top bar.
- Confirm the action. The status should change to "Stopped (deallocated)", which is the correct state.
To turn it back on:
- In the Azure portal, access your VM.
- Click "Start".
- Wait 1-2 minutes. The VM will be ready when the status changes to "Running".
Automation tip: Azure allows you to schedule automatic VM shutdown. On the VM page, look for "Auto-shutdown" in the side menu and configure the time that makes sense for you (e.g., midnight every day). Good for not forgetting to turn off the VM!
Sending Files with FileZilla (SFTP)
With the server configured, you will need to send files to it — your application code, images, templates, or any other content. FileZilla is a free SFTP client with a graphical interface, perfect for this without needing to use the command line.
SFTP ≠ FTP: Always use SFTP (SSH File Transfer Protocol), never simple FTP. SFTP uses the same SSH channel to transfer files with full encryption. Classic FTP transmits everything in plain text — including your password — and should not be used on production servers.
Installing FileZilla
- Go to filezilla-project.org and download the FileZilla Client (not the Server).
- Run the installer and follow the standard steps (Next → I Agree → Next → Install).
Connecting to the server via SFTP
Open FileZilla. In the quick connect bar at the top of the window, fill in the fields:
- Host:
sftp://IP_PUBLICO_DA_VM— thesftp://prefix is mandatory to indicate that we want SFTP, not FTP. - Username: the user you created in Azure.
- Password: the password defined during VM creation.
- Port:
22
Click "Quickconnect".
On the first connection, FileZilla displays a window asking if you trust the server's key — click "OK" (or check "Always trust this host" to not see this warning again).
Connected! The screen is divided into two panes: the left pane shows the files on your computer and the right pane shows the files on the VM. To send a file or folder, drag it from the left pane to the right — or right-click on it and choose "Upload".
Where to put the website files
Your website files should be in /var/www/html/. To navigate to this folder in FileZilla, click in the "Remote site" field (right pane, top part), type /var/www/html and press Enter.
If FileZilla shows a permission error when trying to create or upload files in /var/www/html/, run the commands below in the SSH terminal (replacing seu_usuario with your actual user):
chown -R seu_usuario:www-data /var/www/html
chmod -R 775 /var/www/html
This step has already been covered in the permissions section, but it's worth checking if you still encounter errors.
Tip: Save the connection for future use
To avoid typing the IP and password every time, save the connection in the Site Manager:
- Go to File → Site Manager (or press Ctrl+S).
- Click on "New site" and give it a name (e.g.,
vm-azure). - Under Protocol, select "SFTP — SSH File Transfer Protocol".
- Fill in Host (only the IP, without
sftp://here), User, and Password. - Click on "Connect". The connection will be saved for one-click access next time.
Pointing a domain to the VM
With the server configured and the static IP defined, it's time to use a real domain instead of the IP. See the complete flow:
Registering a domain
For .com.br domains, registration is done through Registro.br (the official body in Brazil):
- Access registro.br, create an account, and search for the availability of the desired domain.
- The cost is R$ 40/year for
.com.brdomains. - After payment, the domain becomes active within 24 hours (usually in minutes).
For international domains (.com, .net, .io, etc.), use registrars like Namecheap or GoDaddy.
Pointing DNS to the VM via Cloudflare
The most practical way is to use Cloudflare as DNS (free), which we will configure in detail in the next section. The flow is:
- Create a free account on Cloudflare and add your domain.
- Cloudflare will give you 2 nameservers (e.g.,
ava.ns.cloudflare.comandbob.ns.cloudflare.com). - In the Registro.br panel, access your domain → DNS → change the default nameservers to Cloudflare's.
- Back in Cloudflare, add an A type DNS record:
- Name:
@(represents the root domain, e.g.,yourdomain.com.br) - Content (IPv4): the static IP of your VM in Azure
- Proxy: activate the orange icon, which enables Cloudflare's CDN and DDoS protection
- Name:
- Also add a CNAME record:
- Name:
www - Target:
@(or the root domain name) - Proxy: active
- Name:
Nameserver propagation can take from a few minutes to 48 hours, but usually happens in less than 1 hour. To check if it has already propagated, use the website dnschecker.org and verify if the A records are pointing to the correct IP in various regions of the world.
Optimizing with Cloudflare
Now that your server is up and running, let's add a touch of magic to make it faster and more secure with Cloudflare. Cloudflare acts as a CDN (Content Delivery Network), firewall, and performance optimizer, all for free on basic plans.
How it works and how to apply
- Create a Cloudflare account: Access cloudflare.com and add your domain.
- Change Nameservers: Cloudflare will instruct you to change your domain's nameservers to theirs. This is crucial — all your website's traffic will now be routed through Cloudflare.
- DNS Configuration: Cloudflare will automatically import your existing DNS entries. Make sure that the
Arecord for your main domain and thewwwsubdomain points to the public IP of your Azure VM.
With Cloudflare active, your site already benefits from basic DDoS protection and automatic caching for static assets (CSS, JS, images).
Cache Rules for HTML Pages
For an even greater performance boost, we can configure Cloudflare to cache HTML pages. This is great for sites with content that doesn't change frequently (blogs, institutional websites) and can drastically reduce loading time.
Follow the steps to create a Cache Rule:
- In the Cloudflare dashboard, select your domain.
- Go to the "Rules" section and then to "Cache Rules".
- Click on "Create rule".
- In the condition field, define the URL pattern you want to cache. For example:
seudominio.com.br/*(to cache everything) orseudominio.com.br/blog/*(only the blog section). - Under "Cache status", select "Eligible for cache".
- Under "Edge TTL", set a time-to-live for the cache (e.g., 1 hour, 4 hours, 1 day). This means Cloudflare will serve the cached page for this period before fetching a new version from your server.
- Click on "Deploy".
Result: With this rule, Cloudflare will store a copy of your HTML pages on its edge servers. When a user requests a page, if it's cached and within the TTL, Cloudflare will deliver it instantly — without even touching your VM in Azure. This means a much faster website with less server load!
ATTENTION: Be very careful when applying full page caching to sites with dynamic content, login areas, or shopping carts. This can cause serious problems, such as users viewing information from other users. Use this rule only on static pages or sections that you are sure can be cached equally for all visitors!
Complete Script
To make it easier, here is the complete script with all the commands we used to configure the environment:
#!/bin/bash
# ================================================================
# Configuration script: Debian 12 (Bookworm) + Apache + PHP 8.5
# + OPcache + ODBC SQL Server Drivers
# ================================================================
## Elevate privileges to root ##
sudo su
## Update repositories ##
apt-get update
## Install Apache and dependencies ##
apt-get --yes install build-essential autoconf flex bison
apt-get --yes install apache2 apache2-dev libapache2-mod-evasive apache2-utils
apt-get --yes install libpng-dev zlib1g zlib1g-dev libxml2 libxml2-dev
apt-get --yes install openssl libssl-dev libgd-dev
apt-get --yes install vim curl libcurl4 libcurl4-openssl-dev
apt-get --yes install libfreetype6-dev libreadline-dev sqlite3
apt-get --yes install rpl zip libzip-dev libbz2-dev unzip libldap2-dev pwgen
apt-get --yes install unixodbc unixodbc-dev s3cmd
## Enable htaccess ##
rpl -e "\n\tOptions Indexes FollowSymLinks\n\tAllowOverride None" \
"\n\tOptions FollowSymLinks\n\tAllowOverride All" \
/etc/apache2/apache2.conf
echo "LimitRequestLine 100000" >> /etc/apache2/apache2.conf
## Enable Apache modules ##
a2dismod mpm_event
a2dismod mpm_worker
a2enmod mpm_prefork
a2enmod rewrite
a2enmod headers
## Add PHP repository (Ondřej Surý) ##
apt install apt-transport-https lsb-release ca-certificates curl gnupg -y
curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" \
| tee /etc/apt/sources.list.d/php.list
apt update
## Install PHP 8.5 ##
apt install php8.5 php8.5-cli php8.5-{bz2,curl,mbstring,intl,xml,gd,zip,mysql,opcache,readline} -y
apt install php-pear wget git php-cgi php-common php-net-socket -y
## Configure OPcache ##
cat > /etc/php/8.5/mods-available/opcache.ini << 'EOF'
zend_extension=opcache.so
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=0
EOF
## Install mod_php for Apache ##
apt install libapache2-mod-php8.5 -y
a2enmod php8.5
## Add Microsoft repository (ODBC — Debian) ##
curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg
curl -s https://packages.microsoft.com/config/debian/12/prod.list \
| tee /etc/apt/sources.list.d/mssql-release.list
apt-get update
## Install SQL Server ODBC drivers ##
ACCEPT_EULA=Y apt-get -y install msodbcsql18 mssql-tools18
echo 'export PATH="$PATH:/opt/mssql-tools18/bin"' >> ~/.bashrc
source ~/.bashrc
## Install PHP extensions for SQL Server via PECL ##
apt install php8.5-dev php8.5-xml -y
export PHP_PEAR_PHP_BIN=/usr/bin/php8.5
pecl channel-update pecl.php.net
pecl install sqlsrv
pecl install pdo_sqlsrv
## Enable extensions in PHP ##
echo "extension=sqlsrv.so" > /etc/php/8.5/mods-available/sqlsrv.ini
echo "extension=pdo_sqlsrv.so" > /etc/php/8.5/mods-available/pdo_sqlsrv.ini
phpenmod -v 8.5 sqlsrv pdo_sqlsrv
## Final permissions and updates ##
# Replace 'your_user' with your actual user
usermod -a -G www-data seu_usuario
newgrp www-data
apt-get upgrade -y
apt autoremove -y
apt-get install htop ncdu -y
## Configure UFW ##
apt-get install ufw -y
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow http
ufw allow https
ufw --force enable
## Restart Apache ##
systemctl restart apache2
echo ""
echo "======================================================"
echo " Configuração concluída com sucesso!"
echo " PHP $(php -r 'echo PHP_VERSION;') instalado com drivers SQL Server."
echo " OPcache ativo. UFW configurado."
echo "======================================================"
And that's it, everyone!
I hope you enjoyed this post. Best regards and see you next time!
Comentários (0)
Carregando comentários…