En este caso quiero montar un Servidor Web Local
Lo primero es disponer de una Raspberry PI, para este ejercicio tenemos una del modelo 3B con MicroSD de 32Gb.
Se ha descargado la última versión de Raspberry OS Lite x64, para convertila en un servidor web PHP + MariaDB + phpMyAdmin, solo en red local (por ahora), si todo va bien, en 20-30minutos, debería estar todo operativo…. pero no, os vais a olvidar de la contraseña de algo… y tendreis que volver a empezar. Palabrta.
🧱 FASE 0 — Suposiciones
Asumimos que:
- Tienes conocimientos/experiendia en en este mundo
- Ya has instalado el SO y activado el acceso por SSH y…
- Ya entras por SSH
- El sistema está actualizado
- Estás como usuario con privilegios
🔹 FASE 1 — Actualizar sistema (base limpia)
sudo apt update
sudo apt upgrade -y
sudo reboot
🔹 FASE 2 — Instalar NGINX (Servidor Web)
sudo apt install nginx -y
Comprobamos que funciona
Desde un PC en la misma red, con nuestro navegador preferido: http://IP_DE_LA_RASPI <– para conocer la IP, solo tienes que buscarla en tu router, o con AdvanceIP Scanner, pero si no, lo más rápido es conectarle un cable de video (HDMI) y al acabar de iniciarse, te muestra la IP asignada. (Más adelante, veremos para poner una IP fija).
Si todo ha ido bien… veríamos esto en nuestro Navagador:

📌 Guarda/Apunta esta ruta:
/var/www/html
🔹 FASE 3 — Instalar PHP + PHP-FPM (optimizado)
sudo apt install php php-fpm php-cli php-mysql php-json php-curl php-zip php-gd php-mbstring php-xml -y
Comprobamos versión
php -v
A día de hoy: 8.4
PHP 8.4.16 (cli) (built: Dec 18 2025 21:19:25) (NTS)
Copyright (c) The PHP Group
Built by Debian
Zend Engine v4.4.16, Copyright (c) Zend Technologies
with Zend OPcache v8.4.16, Copyright (c), by Zend Technologies
🔹 FASE 4 — Conectar Nginx con PHP
Editamos el site por defecto:
sudo nano /etc/nginx/sites-available/default
Buscamos esta línea (en mi caso, línea 43):
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
Y la dejamos tal que así:
# Add index.php to the list if you are using PHP
index index.php index.html index.htm index.nginx-debian.html;
En el mismo archivo, activamos PHP (dentro del bloque server {}), SOLO DEBEMOS REITRAR LAS # y queraría tal que así:
# pass PHP scripts to FastCGI server
#
location ~ \.php$ {
include snippets/fastcgi-php.conf;
#
# # With php-fpm (or other unix sockets):
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
# # With php-cgi (or other tcp sockets):
# fastcgi_pass 127.0.0.1:9000;
}
⚠️ Si tu PHP es otra versión, compruébalo con: ls /run/php/
Reiniciamos servicios
sudo systemctl restart nginx
sudo systemctl restart php8.4-fpm
🔹 FASE 5 — Test rápido de PHP
sudo nano /var/www/html/info.php
Se abre un archivo vacío y escribimos este contenido:
<?php
phpinfo();
Desde el navegador:
http://IP_DE_LA_RASPI/info.php

✔️ Si ves la página de PHP → perfecto
Luego bórralo (por seguridad):
sudo rm /var/www/html/info.php
🔹 FASE 6 — Instalar MariaDB (MySQL)
sudo apt install mariadb-server mariadb-client -y
- Asegurar instalación:
sudo mysql_secure_installation
Respuestas recomendadas:
- Cambiar root password → sí
- Remove anonymous users → sí
- Disallow root login remotely → sí
- Remove test database → sí
- Reload privilege tables → sí
2. Si el paso uno da error, lo haremos por otro camino:
1️⃣ Entra a MariaDB como root del sistema
sudo mariadb
Si entra sin pedir password → todo correcto.
2️⃣ Asegurar el usuario root (método moderno)
Ejecuta tal cual, una a una, sustituyendo «PASSWORD» por la que quieras, hazlo línea por línea:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'PASSWORD';
FLUSH PRIVILEGES;
📌 Usa una contraseña fuerte y guárdala.
3️⃣ Eliminar accesos inseguros
Hazlo línea por línea:
DELETE FROM mysql.user WHERE User='';
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';
FLUSH PRIVILEGES;
4️⃣ Salir
EXIT;
🔎 Comprobación
Prueba:
mysql -u root -p
Si te pide contraseña y entra → OK.
🔐 FASE 6.1 SEGURIDAD
1️⃣ ❌ No usar «root» en PHP (NUNCA), por eso crearemos un «webuser»
Qué es root en MariaDB
root es:
- Usuario total
- Puede borrar TODAS las bases
- Cambiar usuarios
- Alterar permisos
- Romper el sistema entero
Qué pasa si usas root desde PHP
Si una WebApp tiene:
- Un bug
- Un
includemal hecho - Un
eval - Un
uploadinseguro - Un SQL Injection
👉 El atacante hereda privilegios root
👉 Puede borrar TODAS las bases
👉 Puede crear usuarios ocultos
👉 Puede persistir
📌 Aunque sea red local, el riesgo existe (malware, dispositivos comprometidos, errores humanos).
En cambio, usando webuser
- Acceso solo a sus bases
- Sin privilegios administrativos
- Daños limitados
Ejemplo ideal:
GRANT SELECT, INSERT, UPDATE, DELETE
ON app1_db.*
TO 'webuser'@'localhost';
🎯 Regla de oro
PHP = usuario limitado
Admin = root (solo consola)
2️⃣ No exponer MariaDB a la red
Qué significa “exponer”
Que MariaDB:
- Escuche en
0.0.0.0 - Acepte conexiones desde otras máquinas
Ejemplo peligroso:
bind-address = 0.0.0.0
Riesgos reales
- Ataques de fuerza bruta
- Exploits antiguos
- Escaneos automáticos
- Malware interno en la LAN
📌 MariaDB no tiene firewall propio
📌 Es un objetivo habitual
Configuración segura (la que queremos)
bind-address = 127.0.0.1
Resultado:
- Solo PHP local accede
- phpMyAdmin funciona
- Nadie desde fuera entra
¿Y si algún día lo necesitas?
Entonces:
- VPN
- SSH tunnel
- Firewall estricto
Nunca “abrir por abrir”.
🧑💻 3️⃣ ✅ Usar siempre webuser
Qué es webuser
Un usuario hecho para apps, no para admins.
Características:
- Solo conecta desde
localhost - Solo accede a sus BDs
- Sin
GRANT,DROP USER, etc.
Ejemplo realista
CREATE DATABASE app1_db;
CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'password_seguro';
GRANT SELECT, INSERT, UPDATE, DELETE
ON app1_db.*
TO 'webuser'@'localhost';
FLUSH PRIVILEGES;
Ventaja clave en nuestro escenario (AIStudio de Google + JSON)
Mis futuros proyectos:
- Guardan estado en JSON
- Usan DB para metadatos
👉 webuser va sobrado
👉 Menos RAM
👉 Menos riesgo
👉 Más estabilidad
🔧FASE 6.2 CREAR webuser
1️⃣ Entra como root en mySQL
mysql -u root -p
2️⃣ Crear el usuario (solo local)
CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'PASSWORD_FUERTE';
📌 Usa una contraseña fuerte y guárdala.
3️⃣ Crear una base “contenedor” inicial (opcional)
Si quieres una base común:
CREATE DATABASE apps_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
(O luego hacemos una BD por app, sin problema).
4️⃣ Dar permisos limitados y correctos
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, ALTER
ON apps_db.*
TO 'webuser'@'localhost';
⚠️ NO damos:
DROPGRANT OPTIONSUPER
5️⃣ Aplicar cambios
FLUSH PRIVILEGES;
EXIT;
🔎 Verificación (muy importante)
mysql -u webuser -p
Luego:
SHOW DATABASES;
Debe mostrar:
apps_db

Si es así → perfecto ✅
Salimos:
exit;
🧠 Estado actual del sistema
Ahora tenemos:
- Seguridad correcta incluso en LAN
root→ solo administraciónwebuser→ WebApps / phpMyAdmin- DB lista para trabajar
🔹 FASE 7 — Ajustar MariaDB para Raspberry Pi 3B
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
Justo debajo de [mariadbd] ponermos:
# ===============================
# Optimización para Raspberry Pi
# ===============================
# Escuchar solo en localhost (SEGURIDAD)
bind-address = 127.0.0.1
# Conexiones
max_connections = 20
thread_cache_size = 8
# Buffers generales
key_buffer_size = 16M
table_open_cache = 200
table_definition_cache = 200
# InnoDB (ajustado a 1GB RAM)
innodb_buffer_pool_size = 64M
innodb_log_buffer_size = 8M
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 2
# Timeouts
wait_timeout = 300
interactive_timeout = 300
# Desactivar query cache (obsoleto)
query_cache_type = 0
query_cache_size = 0
# Logs (mínimos)
slow_query_log = 0
🧾 Qué hace cada cosa (explicado rápido)
🔐 Seguridad
bind-address = 127.0.0.1
👉 MariaDB solo accesible localmente (PHP + phpMyAdmin)
🧠 RAM controlada
innodb_buffer_pool_size = 64M
key_buffer_size = 16M
👉 Suficiente para:
- Metadatos
- Usuarios
- Configuración
- Tablas pequeñas
⚡ Rendimiento equilibrado
innodb_flush_log_at_trx_commit = 2
👉 Mucho menos I/O en SD
👉 Riesgo aceptable (solo perderías la última transacción en apagón)
🚦 Concurrencia realista
max_connections = 20
👉 Más que suficiente para tu uso
👉 Evita que MariaDB se coma la RAM
🔄 Guardar y reiniciar
Guarda (CTRL+O, ENTER) y sal (CTRL+X).
Luego:
sudo systemctl restart mariadb
🔎 Verificación
sudo systemctl status mariadb
Debe mostrar:
Active: active (running)
Y prueba:
mysql -u webuser -p
Si entra → optimización correcta ✅
Reinicia:
exit;
sudo systemctl restart mariadb
Ahora tu Raspberry tiene:
- MariaDB ligera
- RAM bajo control
- Seguridad local
- Ideal para JSON + WebApps
- phpMyAdmin estable
🔹 FASE 8 — Instalar phpMyAdmin
1️⃣ Instalar phpMyAdmin sin intentar crear la DB automática
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt install phpmyadmin -y
Esto evita el error
Access denied for user 'root'@'localhost'que da el instalador habitual.
2️⃣ Enlazar phpMyAdmin a Nginx
sudo ln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin
Ahora la URL será, y el acceso con el usuario «webuser»:
http://IP_DE_LA_RASPI/phpmyadmin
3️⃣ Configuración de Nginx para phpMyAdmin
Edita tu site por defecto:
sudo nano /etc/nginx/sites-available/default
Agregamos dentro del bloque server { ... }:
server {
location /phpmyadmin {
root /usr/share/;
index index.php index.html index.htm;
location ~ ^/phpmyadmin/(.+\.php)$ {
try_files $uri =404;
root /usr/share/;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~* ^/phpmyadmin/(.+\.(jpg|jpeg|gif|css|png|js|ico|html|xml|txt))$ {
root /usr/share/;
}
}
}
⚠️ Cambia
php8.4-fpm.socksi tu versión de PHP es distinta (ls /run/php/para comprobar).
Reinicia Nginx:
sudo systemctl restart nginx
4️⃣ Crear base de datos (opcional) para phpMyAdmin
Si quieres usar la base tradicional phpmyadmin:
sudo mysql -u root -p
Dentro de MariaDB:
CREATE DATABASE phpmyadmin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON phpmyadmin.* TO 'webuser'@'localhost' IDENTIFIED BY 'TU_PASSWORD_SEGURA';
FLUSH PRIVILEGES;
EXIT;
También puedes usar cualquier DB ya existente (
apps_db) para gestión, no es obligatorio.
5️⃣ Acceder y probar
Abre en tu navegador:
http://IP_DE_LA_RASPI/phpmyadmin
- Usuario:
webuser - Contraseña: la que pusiste al crear
webuser
Si te has olvidado de la contraseña… o la has guardado mal… así puedes cambiarla:
✔ Si entra → phpMyAdmin funcionando.
Entras como ROOT:
sudo mysql -u root
ALTER USER 'webuser'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('NUEVA_CONTRASEÑA_AQUÍ');
FLUSH PRIVILEGES;
Exit;
6️⃣ Seguridad mínima (LAN)
- MariaDB escucha solo en
127.0.0.1→ phpMyAdmin solo accesible desde LAN - Usuario
webuserlimitado → no puede borrar root ni otras DBs - Opcional: añadir
.htpasswdpara capa extra de login
✅ Estado tras FASE 8
- phpMyAdmin instalado y funcional
- MariaDB segura
- Usuario
webuserconfigurado - WebApps listas para usar DB y JSON
🔹 FASE 9 — Estructura final de WebApps y permisos
1️⃣ Carpeta principal para todas las apps
Creamos un directorio central donde se alojarán todas las WebApps:
sudo mkdir -p /var/www/apps
sudo chown -R www-data:www-data /var/www/apps
sudo chmod -R 750 /var/www/apps
www-data→ usuario de Nginx/PHP750→ propietario tiene permisos completos, grupo puede leer/ejecutar, otros nada
2️⃣ Estructura por aplicación
Cada WebApp tendrá su propia carpeta:
/var/www/apps/
├── app1/
│ ├── index.php
│ ├── config.php # Opcional: parámetros de DB o rutas
│ └── data/
│ ├── users.json
│ ├── sessions.json
│ └── cache/
├── app2/
│ └── ...
- data/ → solo archivos JSON
- cache/ → opcional para datos temporales
- index.php → archivo principal de la WebApp
- config.php → configuración de conexión a DB o rutas locales
⚠️ Recomendación: no guardar contraseñas de root en ningún archivo PHP. Solo webuser.
3️⃣ Permisos internos por aplicación
Para cada app:
sudo mkdir -p /var/www/apps/app1/data
sudo chown -R www-data:www-data /var/www/apps/app1
sudo chmod -R 750 /var/www/apps/app1
- Permite que PHP escriba JSON sin exponer nada a otros usuarios del sistema
- Si varias apps necesitan acceso a la misma DB, no hay problema; solo usa
webuser.
4️⃣ Configuración de conexión a MariaDB desde PHP
Ejemplo de config.php:
<?php
$DB_HOST = '127.0.0.1';
$DB_NAME = 'apps_db';
$DB_USER = 'webuser';
$DB_PASS = 'TU_PASSWORD_SEGURA';
$DB_CHARSET = 'utf8mb4';
try {
$pdo = new PDO("mysql:host=$DB_HOST;dbname=$DB_NAME;charset=$DB_CHARSET", $DB_USER, $DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Exception $e) {
die("Error conectando a la DB: " . $e->getMessage());
}
- PHP accede solo como
webuser→ seguridad - Todas las apps pueden usar este patrón
5️⃣ Estructura JSON para datos de la WebApp
Ejemplo de users.json:
[
{
"id": 1,
"nombre": "SineEtiqueta",
"email": "SineTiqueta@example.com",
"rol": "admin"
},
{
"id": 2,
"nombre": "Usuario1",
"email": "user1@example.com",
"rol": "user"
}
]
Guarda cada tipo de dato en un archivo separado para evitar JSON gigante.
Ejemplo:sessions.json,cache.json,equipos.json, etc.
6️⃣ Mejoras opcionales de seguridad
- No exponer
/datavía web- Opción 1:
.htaccess(si usas Apache) - Opción 2 (mejor en Nginx):
- Opción 1:
location ~ /data/ {
deny all;
}
- Copia de seguridad automática de JSON y DB:
- Scripts cron cada noche
- Guardar en
/home/pi/backups/
7️⃣ Probando la WebApp
- Copia tu app de AIStudio a
/var/www/apps/app1/ - Asegúrate que
index.phpfunciona y que puede escribir en/data - Accede desde navegador:
http://IP_DE_LA_RASPI/apps/app1/
- Verifica:
- Escritura/lectura JSON
- Conexión MariaDB
- Cache opcional si aplica
✅ Estado tras FASE 9
- WebApps aisladas, seguras y ordenadas
- JSON estructurado y accesible solo por PHP
- MariaDB para metadatos, con
webuser - Permisos ajustados para proteger datos
- LAN lista para pruebas
💡 Tip de rendimiento:
Si vas a usar varias apps a la vez, asegúrate de no sobrepasar 4 procesos PHP simultáneos (ya lo ajustamos en la fase de PHP-FPM).
FASE INTERMEDIA – Vamos a crear un mini Cpanel para configurar aspectos de seguridad y gestión de archivos (en local)
