---
# System prepended metadata

title: Déployer un site Web dynamique connecté à Azure SQL / Azure Database

---

# Déployer un site Web dynamique connecté à Azure MySQL

> **Guide pas à pas** — VM Ubuntu + Nginx/Apache + PHP + Azure Database for MySQL Flexible Server
>
> Ce guide documente chaque étape du déploiement, de la création des ressources Azure jusqu'à l'interface finale du site.

---

## Table des matières

1. [Créer la base de données Azure MySQL](#1--créer-la-base-de-données-azure-mysql)
2. [Créer la machine virtuelle (VM)](#2--créer-la-machine-virtuelle-vm)
3. [Résoudre le problème de connexion SSH](#3--résoudre-le-problème-de-connexion-ssh)
4. [Installer et configurer le serveur web](#4--installer-et-configurer-le-serveur-web)
5. [Configurer le réseau (NSG / pare-feu)](#5--configurer-le-réseau-nsg--pare-feu)
6. [Déployer l'application PHP](#6--déployer-lapplication-php)
7. [Configurer la base de données](#7--configurer-la-base-de-données)
8. [Construire le dashboard (interface finale)](#8--construire-le-dashboard-interface-finale)

---

## 1 — Créer la base de données Azure MySQL

### 1.1 — Accéder au portail Azure et rechercher le service

Dans le portail Azure, recherchez **"Azure Database for MySQL"** puis cliquez sur **Créer**.

![Recherche du service Azure Database for MySQL dans le portail](https://hackmd.io/_uploads/S1Bhv78HWx.png)

> Ici, on accède au service Azure Database for MySQL depuis la barre de recherche du portail Azure.

![Sélection du type de serveur flexible](https://hackmd.io/_uploads/ryhVuQUHWe.png)

> On choisit le mode **Serveur flexible**, qui est la version recommandée par Azure (plus économique et plus performante).

### 1.2 — Configurer le serveur MySQL

![Formulaire de création du serveur MySQL — informations de base](https://hackmd.io/_uploads/BkQO_78SZe.png)

> Remplissez les champs principaux : nom du serveur (ex: `mysqlwebapp123`), la région, et les identifiants admin (utilisateur + mot de passe).

#### Création avancée

![Options de création avancée du serveur](https://hackmd.io/_uploads/ByILKmIHbg.png)

> Dans les options avancées, vous pouvez ajuster le tier de calcul, le stockage et la version MySQL. Pour un projet de test, le tier **Burstable (B1ms)** est suffisant.

![Configuration du calcul et du stockage](https://hackmd.io/_uploads/rk2UY7LHbg.png)

> Sélectionnez la taille de calcul et le stockage adaptés à votre budget. Le mode Burstable est le plus économique.

### 1.3 — Configuration réseau

![Paramètres réseau du serveur MySQL](https://hackmd.io/_uploads/HkFAFQLB-e.png)

> Configuration de l'accès réseau au serveur de base de données.

![Options de connectivité](https://hackmd.io/_uploads/rkXBFQLHWx.png)

> On choisit l'accès public pour pouvoir se connecter depuis notre VM.

![Règles de pare-feu de la base de données](https://hackmd.io/_uploads/BypucQLSZe.png)

> Les règles de pare-feu définissent quelles adresses IP peuvent accéder au serveur MySQL.

![Récapitulatif de la configuration réseau](https://hackmd.io/_uploads/rJeccmUrZx.png)

> **Important :** On clique sur **"Créer un serveur sans règle de pare-feu"** car la VM n'est pas encore créée. On ajoutera la règle plus tard avec l'IP publique de la VM.

### 1.4 — Validation et création

![Récapitulatif avant création du serveur MySQL](https://hackmd.io/_uploads/ByE3iQLBbl.png)

> Vérifiez le récapitulatif de toutes les options avant de cliquer sur **Créer**.

![Déploiement du serveur MySQL en cours](https://hackmd.io/_uploads/BkSAi7UB-l.png)

> Le déploiement prend environ 2-5 minutes. Une fois terminé, le serveur est prêt.

---

## 2 — Créer la machine virtuelle (VM)

### 2.1 — Configuration de base

![Formulaire de création de la VM — onglet Basics](https://hackmd.io/_uploads/r1IIRmIB-l.png)

> On crée une VM Ubuntu Server. Choisissez la même région que votre serveur MySQL pour minimiser la latence.

![Choix de l'image et de la taille de la VM](https://hackmd.io/_uploads/HJ3IRXUSWx.png)

> Sélectionnez **Ubuntu Server 22.04 LTS** comme image. Pour la taille, une **B1s** ou **B2s** suffit largement pour un site web léger.

### 2.2 — Configuration du disque

![Sélection du type de disque](https://hackmd.io/_uploads/BJonJNIHWl.png)

> **Astuce économique :** Changez le disque de **Premium SSD** à **Standard SSD** pour réduire les coûts. La différence de performance est négligeable pour notre usage.

### 2.3 — Configuration réseau

![Configuration du réseau virtuel et de l'IP publique](https://hackmd.io/_uploads/BycZg48Bbe.png)

> Azure crée automatiquement un réseau virtuel, un sous-réseau et une IP publique. Laissez les valeurs par défaut.

![Règles de port entrant (NSG)](https://hackmd.io/_uploads/rJIhvNLB-e.png)

> Autorisez le **port SSH (22)** dans les règles de sécurité. On ajoutera le port HTTP (80) après.

![Paramètres de gestion de la VM](https://hackmd.io/_uploads/HklRPVUr-x.png)

> Options de monitoring et de gestion. Les valeurs par défaut conviennent pour un projet de test.

### 2.4 — Authentification SSH

![Configuration de la clé SSH](https://hackmd.io/_uploads/rkn8BPLSZl.png)

> Choisissez l'authentification par **clé SSH** (plus sécurisé qu'un mot de passe). Azure peut générer la paire de clés pour vous.

![Téléchargement de la clé privée SSH](https://hackmd.io/_uploads/B1cT5NUrWl.png)

> **Téléchargez et conservez la clé privée** (fichier `.pem`). Vous en aurez besoin pour chaque connexion SSH. Ne la partagez jamais.

### 2.5 — Validation et création

![Récapitulatif et validation de la VM](https://hackmd.io/_uploads/rJp3lBLSbx.png)

> Vérifiez le récapitulatif puis cliquez sur **Créer**. Le déploiement prend environ 1-2 minutes.

---

## 3 — Résoudre le problème de connexion SSH

### 3.1 — Le problème

Après la création de la VM, la connexion SSH peut échouer avec un timeout.

> ❌ **Rien n'atteint la VM depuis Internet**
> (`TcpTestSucceeded = False` + Ping timeout)
>
> Ce n'est PAS un problème de clé SSH, ni de Linux, ni du port 22.
> 👉 **C'est le routage public Azure qui est cassé** — symptôme classique d'une IP publique mal attachée (fréquent après la création d'Azure Bastion).

### 3.2 — La solution : recréer l'IP publique

Dissociez l'IP publique actuelle de la carte réseau de la VM, puis créez-en une nouvelle.

![Dissociation de l'ancienne IP et création d'une nouvelle](https://hackmd.io/_uploads/S1CaxHUB-g.png)

> Dans les paramètres de la carte réseau (NIC), on dissocie l'ancienne IP publique et on en attache une nouvelle.

### 3.3 — Connexion réussie

![Connexion SSH réussie après le changement d'IP](https://hackmd.io/_uploads/SJQjgSLB-l.png)

> La connexion SSH fonctionne maintenant avec la nouvelle IP publique.

![Terminal SSH connecté à la VM Ubuntu](https://hackmd.io/_uploads/Hk_lbBISWg.png)

> On est connecté à la VM via SSH. On peut maintenant installer le serveur web.

---

## 4 — Installer et configurer le serveur web

### 4.1 — Mettre Ubuntu à jour

Dans le terminal SSH :

```bash
sudo apt update && sudo apt upgrade -y
```

### 4.2 — Installer Nginx

```bash
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
```

### 4.3 — Tester l'accès web

Ouvrez dans votre navigateur : `http://VOTRE_IP_PUBLIQUE`

Vous devriez voir la page **"Welcome to nginx"**.

![Page d'accueil Nginx — le serveur web fonctionne](https://hackmd.io/_uploads/S1w2-rIBZx.png)

> ✅ Si vous voyez cette page, le serveur web est opérationnel.

### 4.4 — Si la page ne s'affiche pas

![Échec de l'accès — la page ne charge pas](https://hackmd.io/_uploads/B1jNfBLHWl.png)

> ❌ Si la page ne s'affiche pas, c'est un problème de règles réseau (NSG). Le port 80 n'est pas ouvert.

---

## 5 — Configurer le réseau (NSG / pare-feu)

### 5.1 — Ouvrir le port HTTP (80) dans le NSG

![Accéder aux paramètres réseau de la VM](https://hackmd.io/_uploads/S1UBzBLrbx.png)

> Allez dans les paramètres réseau de la VM pour modifier les règles du NSG (Network Security Group).

![Modification de la règle de sécurité entrante](https://hackmd.io/_uploads/HyqBwHUrWg.png)

> Après avoir changé d'IP publique, il faut aussi **mettre à jour les règles NSG** pour autoriser le trafic HTTP sur le port 80.

### 5.2 — Ajouter la règle de pare-feu pour MySQL

![Ajout de la règle pour le port 80](https://hackmd.io/_uploads/H1MhPrLHbl.png)

> Créez une nouvelle règle entrante autorisant le **port 80 (HTTP)** depuis n'importe quelle source.

### 5.3 — Mettre à jour le pare-feu de la base de données

![Mise à jour des règles de pare-feu Azure MySQL](https://hackmd.io/_uploads/HJSViH8rbl.png)

> N'oubliez pas d'ajouter l'**IP publique de la VM** dans les règles de pare-feu du serveur Azure MySQL. Sans ça, PHP ne pourra pas se connecter à la base.

### 5.4 — Vérification — Nginx accessible

![Page Nginx accessible après configuration du NSG](https://hackmd.io/_uploads/ryuHoBUS-l.png)

> ✅ Après la configuration correcte du NSG, la page Nginx s'affiche dans le navigateur.

---

## 6 — Déployer l'application PHP

### 6.1 — Préparer le dossier du site

```bash
cd /var/www/html
sudo mkdir abder-blog
sudo chown -R $USER:$USER abder-blog
cd abder-blog
```

### 6.2 — Créer le fichier de configuration de la base de données

```bash
nano config.php
```

Collez le contenu suivant (adaptez avec vos identifiants) :

```php
<?php
$host   = "mysqlwebapp123.mysql.database.azure.com";
$db     = "abder_blog";
$user   = "abderapp";
$pass   = "VotreMotDePasse";
$ssl_ca = "/etc/mysql/certs/DigiCertGlobalRootCA.crt.pem";

try {
    $pdo = new PDO(
        "mysql:host=$host;dbname=$db;charset=utf8mb4",
        $user,
        $pass,
        [
            PDO::MYSQL_ATTR_SSL_CA            => $ssl_ca,
            PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
            PDO::ATTR_ERRMODE                  => PDO::ERRMODE_EXCEPTION
        ]
    );
} catch (PDOException $e) {
    die("Erreur DB: " . $e->getMessage());
}
```

> 🔒 **Sécurité** : ne publiez jamais ce fichier sur un dépôt public. Il contient vos identifiants.

### 6.3 — Créer la page d'accueil (test de connexion)

```bash
nano index.php
```

```php
<?php
require "config.php";
$stmt = $pdo->query("SELECT COUNT(*) FROM posts");
$posts = $stmt->fetchColumn();
?>
<!DOCTYPE html>
<html>
<head><title>Abder Blog</title></head>
<body>
    <h1>Bienvenue sur Abder Blog 🚀</h1>
    <p>Connexion MySQL : OK</p>
    <p>Articles dans la base : <?= $posts ?></p>
</body>
</html>
```

### 6.4 — Test dans le navigateur

Ouvrez : `http://VOTRE_IP_PUBLIQUE/abder-blog/`

![Page de test — connexion MySQL réussie](https://hackmd.io/_uploads/rkdijSIrbg.png)

> ✅ La page affiche le nombre d'articles dans la base. La connexion entre PHP et Azure MySQL fonctionne.

---

## 7 — Configurer la base de données

### 7.1 — Vérifications dans le portail Azure

![Accès à la base de données via le portail Azure](https://hackmd.io/_uploads/HJmnoS8HWg.png)

> On peut vérifier l'état de notre serveur MySQL depuis le portail Azure.

![Vue d'ensemble du serveur Azure MySQL](https://hackmd.io/_uploads/HkSpnHUr-g.png)

> Le dashboard du serveur montre les métriques de connexion, le statut et les paramètres.

![Configuration du pare-feu du serveur MySQL](https://hackmd.io/_uploads/rJynWL8H-g.png)

> Vérifiez que l'IP de votre VM est bien autorisée dans les règles de pare-feu du serveur.

![Paramètres du serveur MySQL](https://hackmd.io/_uploads/Skz3_PLBbe.png)

> Les paramètres de configuration du serveur (version MySQL, SSL, etc.).

![Connexion réussie à la base de données](https://hackmd.io/_uploads/B1Hi_P8S-g.png)

> Confirmation que la base de données est bien accessible et fonctionnelle.

### 7.2 — Se connecter à MySQL depuis la VM

```bash
mysql -h mysqlwebapp123.mysql.database.azure.com -u abderapp -p abder_blog
```

### 7.3 — Créer la table `messages` et insérer des données

```sql
CREATE TABLE messages (
    id INT PRIMARY KEY AUTO_INCREMENT,
    contenu VARCHAR(255),
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO messages (contenu) VALUES
    ('Message 1'),
    ('Message 2'),
    ('Message 3'),
    ('Message 4'),
    ('Message 5');

SELECT * FROM messages;
```

Puis quittez : `exit;`

### 7.4 — Vérifier les données sur le site

![Le site affiche les messages de la base de données](https://hackmd.io/_uploads/BJROxuIrWe.png)

> ✅ Les messages insérés en base apparaissent maintenant sur le site web.

![Liste des messages affichée dans le navigateur](https://hackmd.io/_uploads/BJOFlu8S-l.png)

> L'interface commence à prendre forme avec les données dynamiques provenant d'Azure MySQL.

![Interface du site avec les données](https://hackmd.io/_uploads/SyAs-dLr-x.png)

> Vue de l'interface du site affichant les messages. On peut voir le CRUD basique fonctionner.

### 7.5 — Installer l'extension PHP mbstring

Si vous rencontrez une erreur `mb_strlen()` :

```bash
sudo apt update
sudo apt install -y php-mbstring
sudo systemctl restart apache2
```

Vérification :

```bash
php -m | grep mbstring
```

![Extension mbstring installée avec succès](https://hackmd.io/_uploads/rkp5LOwHZl.png)

> ✅ L'extension `mbstring` est active. Les fonctions multibyte PHP fonctionnent correctement.

### 7.6 — Recréer la table avec l'encodage correct

Si nécessaire, recréez la table avec l'encodage `utf8mb4` pour supporter les emojis et caractères spéciaux :

```bash
mysql -h mysqlwebapp123.mysql.database.azure.com -u abderapp -p
```

```sql
SHOW DATABASES;
USE abder_blog;
SHOW TABLES;

CREATE TABLE IF NOT EXISTS messages (
    id INT PRIMARY KEY AUTO_INCREMENT,
    contenu VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

![Table messages créée avec l'encodage utf8mb4](https://hackmd.io/_uploads/rJhuPdwSZl.png)

> ✅ La table `messages` est maintenant créée avec l'encodage `utf8mb4_unicode_ci`, compatible avec les emojis.

---

## 8 — Construire le dashboard (interface finale)

### 8.1 — Architecture des fichiers

```
/var/www/html/abder-blog/
├── public/
│   ├── index.php       ← Dashboard principal
│   ├── add.php         ← Ajout de messages (POST + CSRF)
│   └── delete.php      ← Suppression (POST + CSRF + bulk)
├── src/
│   └── db.php          ← Connexion PDO
└── config.php          ← Configuration BDD
```

### 8.2 — Créer le dashboard (`public/index.php`)

```bash
sudo nano /var/www/html/abder-blog/public/index.php
```

Le dashboard comprend les fonctionnalités suivantes :

| Fonctionnalité | Description |
|---|---|
| KPIs | Total des messages, dernières 24h, 7 jours |
| Composer | Formulaire d'ajout avec protection CSRF |
| Analytics | Graphique Chart.js (messages/jour sur 7 jours) |
| Intelligence | Analyse des termes les plus fréquents |
| Activity Feed | Liste paginée avec vue cartes ou tableau |
| Bulk Actions | Sélection et suppression en masse |
| Command Palette | Navigation rapide avec `Ctrl+K` |
| Thème | Bascule clair/sombre |

**Technologies utilisées :**

| Technologie | Usage |
|---|---|
| Bootstrap 5.3 | Mise en page et composants |
| Bootstrap Icons | Iconographie |
| Chart.js | Graphiques analytics |
| Google Fonts (Inter) | Typographie |
| PHP PDO | Requêtes SQL sécurisées |
| CSRF Token | Protection des actions POST |

### 8.3 — Créer `add.php` (ajout de messages)

```bash
sudo nano /var/www/html/abder-blog/public/add.php
```

```php
<?php
session_start();
require __DIR__ . '/../src/db.php';
$pdo = db();

$csrf = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $csrf)) {
    header("Location: index.php?err=" . urlencode("CSRF invalide."));
    exit;
}

$contenu = trim($_POST['contenu'] ?? '');
if ($contenu === '') {
    header("Location: index.php?err=" . urlencode("Le contenu est vide."));
    exit;
}
if (mb_strlen($contenu) > 255) {
    header("Location: index.php?err=" . urlencode("Max 255 caractères."));
    exit;
}

$stmt = $pdo->prepare("INSERT INTO messages (contenu) VALUES (:c)");
$stmt->execute([':c' => $contenu]);

header("Location: index.php?ok=1");
exit;
```

### 8.4 — Créer `delete.php` (suppression individuelle et en masse)

```bash
sudo nano /var/www/html/abder-blog/public/delete.php
```

```php
<?php
session_start();
require __DIR__ . '/../src/db.php';
$pdo = db();

$csrf = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $csrf)) {
    header("Location: index.php?err=" . urlencode("CSRF invalide."));
    exit;
}

// Bulk delete
$bulk = trim($_POST['bulk_ids'] ?? '');
if ($bulk !== '') {
    $ids = array_filter(array_map('intval', explode(',', $bulk)));
    $ids = array_values(array_unique(array_filter($ids, fn($x) => $x > 0)));
    if (count($ids) > 0) {
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $stmt = $pdo->prepare("DELETE FROM messages WHERE id IN ($placeholders)");
        $stmt->execute($ids);
    }
    header("Location: index.php");
    exit;
}

// Single delete
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
    $stmt = $pdo->prepare("DELETE FROM messages WHERE id = :id");
    $stmt->execute([':id' => $id]);
}

header("Location: index.php");
exit;
```

### 8.5 — Recharger le serveur web

```bash
sudo systemctl reload apache2
```

### 8.6 — Résultat final

![Dashboard final — vue d'ensemble avec KPIs et sidebar](https://hackmd.io/_uploads/r1tMuuPBbe.png)

> L'interface finale du dashboard **"Lux Enterprise Console"**. On voit la sidebar avec le statut système, les KPIs (total messages, dernières 24h, 7 jours) et la navigation par sections.

![Dashboard final — Activity Feed et Analytics](https://hackmd.io/_uploads/SymQddwHWx.png)

> La partie inférieure du dashboard avec le flux d'activité (liste des messages), les contrôles de filtrage/recherche, et le graphique analytics Chart.js sur 7 jours.

---

## Récapitulatif de l'architecture

```
┌─────────────────┐         ┌──────────────────────────┐
│   Navigateur    │ ──HTTP──▶│  VM Ubuntu (Azure)       │
│   (Client)      │         │  ├── Nginx / Apache       │
└─────────────────┘         │  ├── PHP-FPM              │
                            │  └── Application PHP      │
                            └──────────┬───────────────┘
                                       │ SSL/TLS
                            ┌──────────▼───────────────┐
                            │  Azure Database for MySQL │
                            │  (Flexible Server)        │
                            └──────────────────────────┘
```

### Sécurité mise en place

| Mesure | Description |
|---|---|
| CSRF Token | Protection de toutes les actions POST |
| PDO Prepared Statements | Protection contre les injections SQL |
| `htmlspecialchars()` | Échappement de toutes les sorties HTML |
| SSL/TLS | Connexion chiffrée entre PHP et MySQL |
| NSG | Filtrage réseau au niveau de la VM |
| Pare-feu MySQL | Restriction d'accès par IP |

---

> **Guide réalisé par Abder** — Déploiement Azure VM + MySQL Flexible Server