Despliegue de Aplicaciones Web en DAW: temario, prácticas resueltas y preguntas de test

Despliegue de Aplicaciones Web es el módulo de segundo de DAW en el que aprendes a poner una aplicación en marcha en un servidor: instalar y configurar un servidor web (Apache o Nginx) y un servidor de aplicaciones (Tomcat), publicar varios sitios con virtual hosts, activar HTTPS con certificados, transferir ficheros por FTP o SFTP, entender cómo se resuelven los nombres con DNS y llevar el código con Git. Para aprobarlo tienes que saber hacerlo con comandos en Linux y explicar qué hace cada línea de la configuración que escribes.
Lo que lo hace difícil no es programar, porque casi no se programa. Es que un fallo no da un error claro: el navegador enseña la página por defecto, un 403 o un "no se puede acceder al sitio", y el problema puede estar en el fichero de configuración, en los permisos, en el DNS, en el firewall o en la caché del navegador. Aquí tienes qué entra, seis prácticas resueltas, las preguntas de test más repetidas, los errores que más vemos en clase y cómo prepararlo.
Qué entra en Despliegue de Aplicaciones Web
Despliegue de Aplicaciones Web es el módulo 0614 del título de DAW (Real Decreto 686/2010) y se da en segundo curso. El software concreto (Apache o Nginx, vsftpd o ProFTPD, Docker o no) y el peso de cada bloque cambian según la comunidad y el centro:
| Bloque | Qué te piden | Lo que más falla |
|---|---|---|
| Arquitecturas web | Modelo cliente-servidor, arquitectura en capas, instalar un servidor web y uno de aplicaciones | No saber qué hace cada pieza ni por qué puerto escucha |
| Administración de servidores web | Módulos, virtual hosts, autenticación, control de acceso, HTTPS y certificados | Editar la configuración y no activar el sitio ni recargar |
| Servidores de aplicaciones | Desplegar un .war en Tomcat, gestor de aplicaciones, logs, cooperación con Apache o Nginx |
Aplicaciones con javax en Tomcat 10, que usa jakarta |
| Transferencia de ficheros | Servidor FTP, usuarios, acceso anónimo, modo activo y pasivo, conexión segura | Confundir FTPS con SFTP; el modo pasivo bloqueado por el firewall |
| Servicios de red | Resolución de nombres (DNS), servicio de directorio (LDAP), autenticación centralizada | No saber que /etc/hosts se consulta antes que el DNS |
| Documentación y control de versiones | Git (ramas, remotos, historial), documentar la instalación | Subir contraseñas o el .env al repositorio |
| Contenedores (según centro) | Dockerfile y docker compose |
Usar localhost para conectar un contenedor con otro |
Lo normal es que se evalúe con prácticas en una máquina virtual con Ubuntu o Debian y un examen teórico, que en muchos centros a distancia es tipo test. Por eso tanta gente repasa con cuestionarios de Daypo.
Servidor web y servidor de aplicaciones: la diferencia que siempre preguntan
Un servidor web (Apache, Nginx) recibe peticiones HTTP y devuelve ficheros estáticos, o pasa la petición a otro programa como el intérprete de PHP. Un servidor de aplicaciones (Tomcat, WildFly) ejecuta aplicaciones de una plataforma concreta, en Tomcat servlets y JSP de Java, y gestiona su despliegue, sus sesiones y su ciclo de vida.
Lo habitual es combinarlos: el servidor web atiende los puertos 80 y 443, sirve lo estático, gestiona HTTPS y reenvía lo dinámico al servidor de aplicaciones. Eso es un proxy inverso.
Seis prácticas típicas resueltas
Los comandos son para Ubuntu 24.04 o Debian 12/13. Comprueba cada resultado con curl -I antes de abrir el navegador.
1. Dos sitios en el mismo servidor con virtual hosts de Apache
Enunciado: el servidor debe responder con contenidos distintos para tienda.test y blog.test. Configura el sitio de la tienda, sin listado de directorios y con sus propios logs.
sudo apt install apache2
sudo mkdir -p /var/www/tienda.test/public_html
echo '<h1>Tienda</h1>' | sudo tee /var/www/tienda.test/public_html/index.html
Fichero /etc/apache2/sites-available/tienda.test.conf:
<VirtualHost *:80>
ServerName tienda.test
ServerAlias www.tienda.test
DocumentRoot /var/www/tienda.test/public_html
<Directory /var/www/tienda.test/public_html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/tienda_error.log
CustomLog ${APACHE_LOG_DIR}/tienda_access.log combined
</VirtualHost>
sudo a2ensite tienda.test.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
# Sin DNS, el nombre se resuelve con /etc/hosts. Si navegas desde otro equipo,
# añade la línea en ese equipo con la IP del servidor en lugar de 127.0.0.1
echo '127.0.0.1 tienda.test www.tienda.test' | sudo tee -a /etc/hosts
curl -I http://tienda.test
El sitio del blog es el mismo fichero cambiando nombre y carpeta.
Lo que hay que entender: Apache elige el virtual host por la cabecera Host de la petición, que es el nombre que escribiste en el navegador. Si ningún ServerName ni ServerAlias coincide, responde el primer virtual host cargado para ese puerto; en Ubuntu es 000-default.conf, y por eso tanta gente ve la página "It works!" y cree que su sitio no funciona. Los ficheros se escriben en sites-available y a2ensite crea el enlace en sites-enabled. -Indexes hace que una carpeta sin index.html devuelva 403 en vez de listar sus ficheros, y AllowOverride All solo hace falta si usas .htaccess. Usa el dominio .test, reservado para pruebas.
2. Server block de Nginx con proxy inverso a una aplicación
Enunciado: Nginx sirve los ficheros estáticos de app.test y reenvía todo lo que empiece por /api/ a una aplicación que escucha en el puerto 3000.
La aplicación de prueba, en Node.js, solo devuelve la ruta que le llega:
const http = require("node:http");
const servidor = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
rutaRecibida: req.url,
host: req.headers.host,
ipCliente: req.headers["x-real-ip"] ?? null
}));
});
servidor.listen(3000, "127.0.0.1", () => {
console.log("App escuchando en http://127.0.0.1:3000");
});
Fichero /etc/nginx/sites-available/app.test:
server {
listen 80;
server_name app.test;
root /var/www/app.test/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
access_log /var/log/nginx/app.test.access.log;
error_log /var/log/nginx/app.test.error.log;
}
sudo ln -s /etc/nginx/sites-available/app.test /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl http://app.test/api/productos
Lo que hay que entender: la barra final de proxy_pass cambia la ruta que recibe la aplicación. Con proxy_pass http://127.0.0.1:3000; (sin barra) la aplicación recibe /api/productos. Con proxy_pass http://127.0.0.1:3000/; (con barra) Nginx sustituye /api/ por / y la aplicación recibe /productos. Es la causa de muchos 404 que "no tienen sentido". X-Real-IP y X-Forwarded-For existen porque, detrás de un proxy, la aplicación ve todas las peticiones como si vinieran de 127.0.0.1. Un 502 Bad Gateway significa que Nginx funciona pero la aplicación de atrás no está arrancada o escucha en otro puerto.
3. Desplegar un .war en Tomcat y publicarlo a través de Apache
Enunciado: instala Tomcat, despliega miapp.war y haz que se pueda acceder a ella desde http://tienda.test/miapp/ sin escribir el puerto 8080.
Un .war es un ZIP con una estructura fija:
miapp.war
├── index.html
├── META-INF/
│ └── MANIFEST.MF
└── WEB-INF/
├── web.xml (descriptor de despliegue)
├── classes/ (tus .class)
└── lib/ (los .jar que usa la aplicación)
Un servlet mínimo para Tomcat 10:
package es.codeme;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hola")
public class HolaServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().println("Hola desde Tomcat");
}
}
sudo apt install tomcat10 tomcat10-admin
sudo cp miapp.war /var/lib/tomcat10/webapps/
curl http://localhost:8080/miapp/hola
Para usar el gestor web (/manager/html), añade un usuario en /etc/tomcat10/tomcat-users.xml y reinicia con sudo systemctl restart tomcat10:
<role rolename="manager-gui"/>
<user username="admin" password="cambia-esta-clave" roles="manager-gui"/>
Para publicarla a través de Apache, activa los módulos de proxy y añade dos líneas dentro del virtual host de la práctica 1:
sudo a2enmod proxy proxy_http
ProxyPass /miapp/ http://127.0.0.1:8080/miapp/
ProxyPassReverse /miapp/ http://127.0.0.1:8080/miapp/
Lo que hay que entender: el nombre del fichero decide la ruta: miapp.war se publica en /miapp/ y ROOT.war en la raíz. Tomcat lo descomprime y arranca al copiarlo en webapps. web.xml es el descriptor de despliegue, y lo preguntan aunque con @WebServlet vaya casi vacío. La trampa más común: Tomcat 10 usa jakarta.servlet, y una aplicación compilada con javax.servlet (Tomcat 9 o anterior) no arranca o sus servlets devuelven 404. ProxyPassReverse reescribe las redirecciones de Tomcat para que el navegador no acabe en :8080.
4. HTTPS con certificado autofirmado y con Let's Encrypt
Enunciado: el sitio de la tienda debe funcionar por HTTPS y redirigir a HTTPS cualquier petición que llegue por HTTP.
En la máquina virtual, sin dominio real, se usa un certificado autofirmado:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/tienda.test.key \
-out /etc/ssl/certs/tienda.test.crt \
-subj "/CN=tienda.test" \
-addext "subjectAltName=DNS:tienda.test,DNS:www.tienda.test"
sudo a2enmod ssl
Fichero /etc/apache2/sites-available/tienda.test-ssl.conf:
<VirtualHost *:443>
ServerName tienda.test
ServerAlias www.tienda.test
DocumentRoot /var/www/tienda.test/public_html
SSLEngine on
SSLCertificateFile /etc/ssl/certs/tienda.test.crt
SSLCertificateKeyFile /etc/ssl/private/tienda.test.key
</VirtualHost>
En el virtual host del puerto 80, cambia el DocumentRoot por una redirección:
<VirtualHost *:80>
ServerName tienda.test
ServerAlias www.tienda.test
Redirect permanent / https://tienda.test/
</VirtualHost>
sudo a2ensite tienda.test-ssl.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
sudo ufw allow 'Apache Full'
curl -kI https://tienda.test
Con un dominio real que apunte a tu servidor, Let's Encrypt da el certificado gratis y Certbot configura Apache solo:
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d tienda.com -d www.tienda.com
sudo certbot renew --dry-run
Lo que hay que entender: el navegador avisa con el certificado autofirmado porque no lo firma ninguna autoridad de certificación en la que confíe; el cifrado funciona igual (en curl, -k ignora ese aviso). El subjectAltName es obligatorio: los navegadores actuales comprueban el nombre en ese campo, no en el CN. Let's Encrypt necesita que el dominio apunte a tu IP pública y que el puerto 80 esté abierto para comprobar que es tuyo, así que no sirve para .test ni para una IP privada. Para Nginx: paquete python3-certbot-nginx y certbot --nginx.
5. Dockerfile y docker compose para una aplicación PHP con MySQL
Enunciado: empaqueta la aplicación PHP de la carpeta src en una imagen con Apache y levántala junto a una base de datos MySQL con docker compose.
Dockerfile:
FROM php:8.4-apache
RUN docker-php-ext-install pdo_mysql
COPY src/ /var/www/html/
compose.yaml:
services:
web:
build: .
ports:
- "8080:80"
environment:
DB_HOST: db
DB_NAME: tienda
DB_USER: tienda
DB_PASSWORD: tienda123
depends_on:
- db
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: tienda
MYSQL_USER: tienda
MYSQL_PASSWORD: tienda123
volumes:
- datos:/var/lib/mysql
volumes:
datos:
docker compose up -d --build
docker compose ps
docker compose logs -f web
docker compose down # para y borra los contenedores; los datos siguen en el volumen
Y en PHP, la conexión usa el nombre del servicio:
$pdo = new PDO(
"mysql:host=" . getenv("DB_HOST") . ";dbname=" . getenv("DB_NAME") . ";charset=utf8mb4",
getenv("DB_USER"),
getenv("DB_PASSWORD")
);
Lo que hay que entender: "8080:80" significa puerto 8080 de tu máquina hacia el 80 del contenedor, en ese orden. Dentro de un contenedor, localhost es el propio contenedor, así que la aplicación se conecta a MySQL por el nombre del servicio, db. depends_on solo ordena el arranque: no espera a que MySQL acepte conexiones, y la primera vez la conexión puede fallar unos segundos. Sin el volumen datos, la base de datos se pierde al borrar el contenedor, y docker compose down -v borra también el volumen. Si lo que te cuesta es la parte de PHP, la tienes en cómo aprobar Desarrollo Web en Entorno Servidor.
6. Flujo básico de Git para desplegar
Enunciado: sube el proyecto a un repositorio remoto, desarrolla la configuración HTTPS en una rama, intégrala en main, marca la versión entregada y actualiza el servidor.
# En tu equipo
printf '.env\n*.log\n' > .gitignore
git init
git add .
git commit -m "Primera versión de la tienda"
git branch -M main
git remote add origin git@github.com:usuario/tienda.git
git push -u origin main
git switch -c feature/https
# ... cambios ...
git add tienda-ssl.conf
git commit -m "Añade virtual host HTTPS"
git switch main
git merge feature/https
git push
git tag -a v1.0 -m "Versión entregada"
git push origin v1.0
# En el servidor
git clone git@github.com:usuario/tienda.git /var/www/tienda # la primera vez
git pull # las siguientes, dentro de la carpeta
Lo que hay que entender: git add prepara los cambios, git commit los guarda en tu repositorio local y solo git push los manda al remoto. El .gitignore se crea antes del primer git add: si el .env con contraseñas ya está en un commit, añadirlo después al .gitignore no lo borra del historial. Un tag marca una versión concreta para poder volver a ella. En el servidor no se edita a mano: si alguien cambia ahí un fichero, el siguiente git pull puede fallar por conflicto.
Preguntas de test típicas de Despliegue de Aplicaciones Web
Son las preguntas cortas que más se repiten en exámenes y en cuestionarios tipo Daypo.
| Servicio | Puerto por defecto |
|---|---|
| HTTP / HTTPS | 80 / 443 |
| FTP | 21 (control) y 20 (datos en modo activo) |
| SSH y SFTP | 22 |
| DNS | 53 (UDP y TCP) |
| LDAP / LDAPS | 389 / 636 |
| Tomcat (conector HTTP) | 8080 |
| MySQL | 3306 |
| Código HTTP | Significado |
|---|---|
| 200 | Correcto |
| 301 / 302 | Redirección permanente / temporal |
| 304 | No modificado: el navegador usa su caché |
| 401 | Hace falta autenticarse |
| 403 | Prohibido: el servidor no permite el acceso (a menudo, permisos o Require) |
| 404 | No encontrado |
| 500 | Error interno de la aplicación |
| 502 | El proxy no ha recibido una respuesta válida del servidor de atrás |
| 503 | Servicio no disponible (caído o sobrecargado) |
- ¿Tipos de virtual host? Basados en nombre (cabecera
Host, lo más común), en IP o en puerto. - ¿Descriptor de despliegue?
WEB-INF/web.xmlen una aplicación web Java. - ¿FTP activo o pasivo? En activo, el servidor abre la conexión de datos hacia el cliente desde el puerto 20. En pasivo, el cliente abre las dos conexiones al puerto que le indica el servidor; funciona mejor si el cliente está detrás de un router o un firewall.
- ¿FTPS o SFTP? FTPS es FTP cifrado con TLS. SFTP es otro protocolo, sobre SSH (puerto 22).
- ¿Qué se consulta primero,
/etc/hostso el DNS? Normalmente/etc/hosts; el orden lo fija/etc/nsswitch.conf. - ¿Registros DNS? A (IPv4), AAAA (IPv6), CNAME (alias), MX (correo), NS (servidor de la zona), PTR (inversa, de IP a nombre).
- ¿Consulta recursiva o iterativa? En la recursiva, el servidor devuelve la respuesta final. En la iterativa, devuelve una referencia a otro servidor al que preguntar.
- ¿La autenticación básica es segura? No: las credenciales viajan en Base64, que no es cifrado. Solo con HTTPS. En Apache, los usuarios se crean con
htpasswd. - ¿
git fetchogit pull?fetchdescarga los cambios del remoto sin tocar tu rama;pulllos descarga y los integra.
Los errores que más suspenden en Despliegue de Aplicaciones Web
Son los que más vemos cuando un alumno comparte pantalla y dice "lo he configurado todo y no va".
No mirar los logs. Apache escribe en /var/log/apache2/error.log (y en los logs de cada virtual host), Nginx en /var/log/nginx/error.log y Tomcat en /var/log/tomcat10/. Si un servicio no arranca, systemctl status apache2 y journalctl -u apache2 te dicen por qué. La causa casi siempre está escrita ahí.
Editar y no activar ni recargar. Un fichero en sites-available no hace nada hasta que ejecutas a2ensite (o creas el enlace en Nginx) y recargas. Y al revés: copiar el fichero a sites-enabled en vez de enlazarlo deja dos versiones distintas de la misma configuración.
Permisos y propietario mal puestos. Apache y Nginx se ejecutan como www-data y necesitan leer los ficheros (644) y atravesar las carpetas (755). La solución a un 403 no es chmod 777 ni hacer a www-data dueño de todo: el dueño puede ser tu usuario, y a www-data solo se le da escritura donde la aplicación guarda archivos subidos.
El puerto ya está ocupado. Apache y Nginx instalados a la vez quieren el puerto 80, y el segundo no arranca con "Address already in use". Con sudo ss -tlnp ves qué proceso escucha en cada puerto.
El firewall. Todo funciona con curl dentro del servidor y nada desde fuera. Revisa sudo ufw status y, en la nube, las reglas de red del proveedor.
El /etc/hosts de la máquina equivocada. Si el servidor es una máquina virtual y navegas desde tu ordenador, el nombre va en el fichero hosts de tu ordenador, apuntando a la IP de la máquina virtual (en Windows, C:\Windows\System32\drivers\etc\hosts).
Rutas del DocumentRoot que no coinciden. Una carpeta mal escrita, el DocumentRoot y el <Directory> apuntando a rutas distintas o el index.html en una subcarpeta. Resultado: 403 o 404.
La caché del navegador con HTTPS y redirecciones. El navegador guarda las redirecciones 301 y, si las configuraste, las cabeceras HSTS. Cambias la configuración y sigues viendo lo de antes. Prueba con curl -I, que no tiene caché, o en una ventana privada.
Cómo prepararlo
Monta tu propia máquina virtual con instantáneas. Haz cada práctica desde cero dos veces, la segunda sin apuntes. Si algo se rompe, vuelves a la instantánea.
Escribe tu chuleta de rutas y comandos. Dónde está cada configuración, cómo se activa un sitio, cómo se comprueba la sintaxis (apache2ctl configtest, nginx -t), cómo se recarga y dónde están los logs.
Comprueba cada paso con curl -I. Si sabes leer un 301, un 403 y un 502, sabes por dónde empezar a buscar.
Repasa la teoría con preguntas cortas. Hazte tus propias preguntas de test a partir de los apuntes, además de usar las de Daypo, que a veces tienen respuestas mal marcadas.
Practica Linux y Bash. Media asignatura es moverse por la terminal y entender permisos, igual que en ASIR: mira cómo hacer scripts de Bash sin bloquearte.
Despliega tu proyecto final. Es la mejor práctica posible, y el proyecto final de DAM y DAW suele tener que entregarse desplegado y documentado.
Cuándo pedir ayuda
Si haces las prácticas y sabes explicar cada línea de la configuración, vas bien. Merece la pena buscar ayuda cuando:
- Llevas horas con un 403, un 502 o la página por defecto de Apache y no sabes qué log mirar.
- Copias la configuración de un tutorial, funciona, pero no sabrías explicar qué hace cada directiva.
- En la práctica de Docker o de Tomcat no sabes si el fallo está en Linux, en la red o en el código.
- Tienes Despliegue pendiente y lo necesitas para empezar las prácticas en empresa o entregar el proyecto final.
En nuestras clases de refuerzo para DAM y DAW trabajamos sobre tus enunciados y tu máquina: leemos juntos los logs y lo configuras tú, para que en el examen sepas hacerlo solo. Si estudias ASIR, los servidores web, DNS y FTP se parecen mucho a los de Servicios de Red e Internet, y también los trabajamos en las clases para ASIR y ASIX. Son clases online uno a uno a 25 € la hora, sin matrícula ni permanencia.
Preguntas frecuentes
¿Qué es Despliegue de Aplicaciones Web?
Es el módulo 0614 de segundo curso del ciclo de Desarrollo de Aplicaciones Web (DAW). Enseña a publicar aplicaciones web en un servidor: configurar Apache o Nginx y Tomcat, crear virtual hosts, activar HTTPS, montar un servidor FTP, entender DNS y LDAP, y usar Git para el control de versiones.
¿Qué se da en Despliegue de Aplicaciones Web?
El temario oficial tiene seis bloques: arquitecturas web, administración de servidores web, servidores de aplicaciones, servidores de transferencia de ficheros, servicios de red (DNS y directorio) y documentación y control de versiones. Muchos centros añaden Docker.
¿Es difícil Despliegue de Aplicaciones Web?
Depende de tu soltura con Linux. Hay poco código, pero muchos comandos, rutas y ficheros de configuración, y un error pequeño aparece como un fallo sin relación aparente. Si te mueves bien por la terminal y lees los logs, suele ser de los módulos más llevaderos de segundo de DAW. Tienes una visión general del ciclo en si es difícil DAM o DAW.
¿Sirven los test de Daypo para aprobar Despliegue de Aplicaciones Web?
Sirven para repasar la teoría: puertos, códigos HTTP, DNS o FTP. No sustituyen a las prácticas, porque casi todos los centros evalúan también configurando un servidor. Y como los cuestionarios los crean otros usuarios, algunas respuestas están mal: contrasta con tus apuntes lo que no te cuadre.
¿Qué diferencia hay entre Apache y Nginx?
Los dos son servidores web. Apache es modular, permite configuración por carpeta con .htaccess y procesa las peticiones con distintos modelos de procesos e hilos. Nginx funciona con un modelo asíncrono dirigido por eventos, consume menos memoria con muchas conexiones y es muy habitual como proxy inverso delante de una aplicación.
¿Hay que saber Docker para Despliegue de Aplicaciones Web?
No está en el decreto del título, pero cada vez más centros lo incluyen en las prácticas. Con saber escribir un Dockerfile sencillo, un compose.yaml con una aplicación y una base de datos, y los comandos docker compose up, ps, logs y down, tienes lo que se suele pedir.