Tutorial: instalar (y eliminar) una app Flask en tu VPS
Guía de referencia basada en todo lo que armamos con agro. Usá esto
como receta para cualquier proyecto nuevo — cambiá agro2 por el
nombre que corresponda cada vez.
PARTE A — Instalación desde cero
Paso 0: Elegir un puerto libre (¡el paso que más tiempo nos llevó hoy!)
Antes de escribir una sola línea de código, confirmá qué puertos ya están ocupados en tu VPS:
sudo ss -tlnp
docker ps --format "table {{.Names}}\t{{.Ports}}"
Puertos que ya tenés ocupados en esta VPS (actualizá esta lista si instalás algo nuevo):
| Puerto(s) | Servicio |
|---|---|
| 22 | SSH |
| 80 | nginx |
| 21985 | Relay VisualDesk |
| 21115-21119 (TCP) / 21116 (UDP) | RustDesk |
| 5000-5150 (TCP y UDP) | Traccar |
| 8082, 5055 | Traccar |
| 9090 | agro (este proyecto) |
Elegí un número que no esté en esa lista. Fuera del rango 5000-5150 (que se lo come Traccar entero) y fuera de 21000-22000 (RustDesk + relay), vas bastante seguro. Ideas: 9091, 9092, 9100, 3001, etc.
Paso 1: Crear la carpeta del proyecto
sudo mkdir -p /opt/agro2
sudo chown -R $USER:$USER /opt/agro2
cd /opt/agro2
Paso 2: Preparar tus archivos en tu PC
En tu entorno local, tené listos como mínimo:
- api_server.py (con el puerto elegido en el paso 0, ej. app.run(host="0.0.0.0", port=9091, debug=False))
- index.html (si tu app sirve una página web)
- Cualquier otro archivo que tu proyecto necesite
Importante: debug=False siempre en cualquier servidor expuesto a
internet. El modo debug de Flask abre una consola interactiva
peligrosa si alguien la encuentra.
Paso 3: Subir los archivos con FileZilla
Conectate por SFTP a la VPS y subí los archivos a /opt/agro2/.
Paso 4: Crear el entorno virtual e instalar dependencias
cd /opt/agro2
python3 -m venv venv
source venv/bin/activate
pip install flask
(Si te falta python3-venv, primero: sudo apt install -y python3.12-venv)
Paso 5: Probar a mano antes de automatizar nada
python api_server.py
Si no tira "Address already in use", vas bien. Probá desde el
navegador con http://IP_DE_TU_VPS:PUERTO. Cuando confirmes que
anda, cortá con Ctrl+C.
Paso 6: Crear el servicio systemd (para que quede corriendo solo)
En tu PC, creá un archivo agro2.service con este contenido (ajustando nombres y puerto si hace falta):
[Unit]
Description=Agro2 (Flask)
After=network.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/opt/agro2
ExecStart=/opt/agro2/venv/bin/python api_server.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Ojo con la ubicación: este archivo NO va en /opt/agro2 con el
resto del proyecto. Tiene que ir en /etc/systemd/system/ — es la
carpeta fija donde systemd busca todos los servicios del sistema
(ahí conviven el de Traccar, el del relay, y el tuyo).
Subilo por FileZilla a /opt/agro2/ (temporalmente) y después
movelo con:
sudo mv /opt/agro2/agro2.service /etc/systemd/system/agro2.service
Paso 7: Activar el servicio
sudo systemctl daemon-reload
sudo systemctl enable agro2.service
sudo systemctl start agro2.service
sudo systemctl status agro2.service
Tiene que decir active (running).
Paso 8: Abrir el puerto (en LOS DOS firewalls)
Firewall interno (ufw):
sudo ufw allow PUERTO/tcp
sudo ufw status
Firewall de la nube (panel de Contabo): Entrá al panel web → sección de firewall → agregá regla: - Protocolo: TCP - Puerto: el que elegiste - Origen: Cualquiera
Los dos son necesarios — si falta uno solo, no entra el tráfico.
Paso 9: La prueba real
Cerrá la sesión SSH del todo (exit) y probá desde el navegador:
http://IP_DE_TU_VPS:PUERTO
Si sigue andando con la sesión cerrada, quedó como servicio permanente de verdad — no depende de que tengas una terminal abierta.
PARTE B — Eliminar un proyecto por completo
Cuando quieras dar de baja agro2 (o cualquier proyecto similar) sin
dejar rastros:
1. Parar y deshabilitar el servicio
sudo systemctl stop agro2.service
sudo systemctl disable agro2.service
2. Borrar el archivo de systemd
sudo rm /etc/systemd/system/agro2.service
sudo systemctl daemon-reload
3. Si usaba nginx como intermediario, borrar esa config también
ls /etc/nginx/sites-enabled/ # confirmar el nombre exacto primero
sudo rm /etc/nginx/sites-enabled/NOMBRE_DEL_SITIO
sudo nginx -t # debe decir "syntax is ok" / "test is successful"
sudo systemctl reload nginx
4. Borrar la carpeta del proyecto
sudo rm -rf /opt/agro2
5. Si usaba Docker (docker-compose), bajar contenedores y volúmenes
cd /opt/agro2 # antes de borrar la carpeta en el paso 4
docker compose down -v
docker ps -a | grep -i agro2 # debe salir vacío
docker volume ls | grep -i agro2 # debe salir vacío
⚠️ El flag -v borra también los datos guardados en los volúmenes,
sin posibilidad de recuperarlos. Confirmá antes de correrlo que no
haya nada importante ahí.
6. Cerrar el puerto en ambos firewalls
sudo ufw delete allow PUERTO/tcp
sudo ufw status
Y borrar manualmente la regla correspondiente en el panel de Contabo.
7. Verificación final — confirmar que no queda nada
ls /opt/ | grep agro2 # vacío
ls /etc/systemd/system/ | grep -i agro2 # vacío
sudo ss -tlnp | grep PUERTO # vacío
sudo ufw status | grep PUERTO # vacío
Si los cuatro comandos devuelven vacío, el proyecto quedó eliminado sin dejar ningún rastro en el servidor.
Cuándo hace falta reiniciar el servicio después de modificar archivos
| Archivo que cambiaste | ¿Hace falta systemctl restart? |
|---|---|
Cualquier .py (ej. api_server.py, generar_pdf.py) |
Sí, siempre |
.html sueltos (ej. index.html) |
No — Flask los lee del disco en cada visita |
Archivos dentro de templates/ |
No — se leen de nuevo en cada uso |
Razón: Python carga el código de los .py en memoria una sola vez al
arrancar el servicio, y se queda trabajando con esa copia. Si cambiás
algo ahí, necesita "releerlo" — de ahí el reinicio. El HTML y las
plantillas, en cambio, se leen del disco en cada pedido.
Reiniciar es seguro: el corte dura milisegundos, y como el .service
tiene Restart=always, si el código nuevo tuviera un error de sintaxis
te vas a enterar con systemctl status NOMBRE.service en vez de quedar
el servidor "colgado" a medias.
sudo systemctl restart NOMBRE.service
sudo systemctl status NOMBRE.service
PARTE C — Agregar HTTPS con dominio propio (opcional, pero recomendado)
Esta parte transforma http://IP:PUERTO (funcional pero con avisos de
"no seguro") en algo como https://agro.tudominio.ar (con candado
verde, apto para compartir con terceros). La hicimos real hoy con
agro.anna.ar — quedate tranquilo, cada paso ya está probado.
Requisito previo: un dominio (o subdominio) apuntando a tu VPS
Si ya tenés un dominio propio (como anna.ar), no hace falta comprar
nada nuevo — un subdominio (agro.anna.ar) es gratis y no afecta
en nada al resto de lo que tengas en ese dominio.
Paso 1: confirmar dónde se administra el DNS de tu dominio
nslookup -type=NS tudominio.ar
Esto te dice si el DNS lo maneja NIC.ar directamente, o algún hosting (en nuestro caso, dio Hostinger).
Paso 2: agregar el registro A del subdominio
Entrá al panel correspondiente (en nuestro caso, hPanel de Hostinger → Dominios → tu dominio → Zona DNS) y agregá:
| Campo | Valor |
|---|---|
| Tipo | A |
| Nombre | agro (o el subdominio que quieras) |
| Valor | la IP de tu VPS |
| TTL | dejar el valor por default |
Paso 3: esperar la propagación y confirmar
Puede tardar de minutos a un par de horas. Confirmás así:
nslookup agro.tudominio.ar
Cuando te devuelva la IP de tu VPS, seguís al paso 4.
Paso 4: crear la configuración de nginx para el subdominio
sudo nano /etc/nginx/sites-available/agro.tudominio.ar
⚠️ Ojo con esto: una vez que corras el comando de arriba, la
pantalla de la terminal cambia por completo (vas a ver una franja
azul/gris abajo con ^O Escribir ^X Salir). Recién ahí, cuando
confirmes que estás dentro de esa pantalla de edición, pegás el
siguiente bloque. Si lo pegás en la terminal normal (antes de que
cambie la pantalla), bash va a tirar errores tipo "command not found"
porque intenta ejecutar cada línea como si fuera un comando de Linux.
server {
listen 80;
listen [::]:80;
server_name agro.tudominio.ar;
location / {
proxy_pass http://127.0.0.1:PUERTO;
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;
}
}
(reemplazá PUERTO por el puerto real de tu app, ej. 9090)
Guardar y salir: Ctrl+O, Enter, Ctrl+X.
Verificá que quedó bien escrito antes de seguir:
cat /etc/nginx/sites-available/agro.tudominio.ar
Paso 5: activar el sitio
sudo ln -s /etc/nginx/sites-available/agro.tudominio.ar /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx -t tiene que decir syntax is ok / test is successful.
⚠️ Los dos últimos comandos NO son lo mismo, y los dos son necesarios:
- nginx -t solo revisa que la sintaxis esté bien — no aplica nada.
- systemctl reload nginx es el que efectivamente aplica la configuración nueva. Sin este paso, nginx sigue trabajando con la configuración vieja en memoria, aunque el archivo ya esté bien guardado en disco — por eso a veces "no anda" después de crear un sitio nuevo, aunque todo esté bien escrito: falta este último paso.
Paso 6: abrir el puerto 80 en AMBOS firewalls
sudo ufw allow 80/tcp
Y en el panel de Contabo: regla TCP, puerto 80, origen Anywhere. (Si ya lo tenías abierto de antes para otro proyecto, no hace falta repetirlo — pero confirmalo, porque en limpiezas de otros proyectos es fácil borrarlo sin querer, como nos pasó hoy.)
Paso 7: probar por HTTP antes de pasar a HTTPS
http://agro.tudominio.ar
Si no carga, puede ser que el firewall de Contabo tarde un par de minutos en aplicar la regla nueva — esperá y volvé a probar antes de asumir que algo está mal.
Paso 8: instalar Certbot (una sola vez por servidor, como nginx)
sudo apt install -y certbot python3-certbot-nginx
Paso 9: generar el certificado
sudo certbot --nginx -d agro.tudominio.ar
Te va a pedir:
- Un email (para avisos de vencimiento — el certificado se renueva
solo, pero por las dudas).
- Aceptar términos de servicio (Y).
- Compartir el email con la EFF (opcional, Y o N, no afecta nada).
Si sale bien, termina con un mensaje tipo "Congratulations! You have
successfully enabled HTTPS". Certbot modifica el archivo de nginx
solo, agregando un segundo bloque server con listen 443 ssl y las
rutas a los certificados.
Paso 10: abrir el puerto 443 en AMBOS firewalls
Mismo patrón que el puerto 80 — es un puerto nuevo, hay que abrirlo en los dos lados:
sudo ufw allow 443/tcp
Y en el panel de Contabo: regla TCP, puerto 443, origen Anywhere.
Paso 11: la prueba final
https://agro.tudominio.ar
Si no carga al toque, esperá 1-2 minutos (propagación del firewall de Contabo) y probá en una pestaña de incógnito antes de asumir que algo falló — el navegador a veces cachea el error anterior.
Resumen — checklist HTTPS
- Confirmar dónde se administra el DNS (
nslookup -type=NS) - Agregar registro A del subdominio en ese panel
- Esperar propagación, confirmar con
nslookup - Crear config de nginx para el subdominio (dentro de nano, no en la terminal)
- Activar el sitio (
ln -s+nginx -t+reload) - Abrir puerto 80 en
ufwY en Contabo - Probar por HTTP
- Instalar Certbot (una vez por servidor)
certbot --nginx -d subdominio- Abrir puerto 443 en
ufwY en Contabo - Probar por HTTPS (con paciencia si tarda unos minutos)
- (Opcional) Confirmar que la renovación automática funciona:
sudo certbot renew --dry-run
Nota: Certbot se instala una sola vez, el certificado se pide por cada subdominio
sudo apt install -y certbot python3-certbot-nginx→ una sola vez por servidor (como nginx).sudo certbot --nginx -d nombre.tudominio.ar→ una vez por cada subdominio nuevo que armes, porque cada uno necesita su propio certificado.- La renovación (cada ~60 días, antes de que venzan los 90 días de validez) queda automática, gracias a una tarea programada (
certbot.timer) que Certbot deja configurada solo. Podés verla con:bash sudo systemctl list-timers | grep certbotY probar que funciona (sin cambiar nada de verdad) con:bash sudo certbot renew --dry-run
Resumen — checklist rápido
Instalar:
1. Elegir puerto libre (ss -tlnp + docker ps)
2. mkdir /opt/NOMBRE + permisos
3. Subir archivos (FileZilla)
4. venv + pip install flask
5. Probar a mano
6. Crear .service → moverlo a /etc/systemd/system/
7. daemon-reload + enable + start
8. Abrir puerto en ufw Y en Contabo
9. Probar con la sesión SSH cerrada
Eliminar:
1. stop + disable el servicio
2. Borrar el .service
3. Borrar config de nginx (si aplica)
4. Borrar la carpeta del proyecto
5. docker compose down -v (si aplica)
6. Cerrar puerto en ufw Y en Contabo
7. Verificar que no quede nada
Tutorial: proyecto Dólar Oficial (Contabo)
Referencia completa del proyecto dolar: desde la instalación hasta
el registro automático diario y la exportación de históricos para
PowerCOBOL. Complementa a tutorial_vps_flask.md (el general), pero
acá está todo lo específico de este proyecto en un solo lugar.
Archivos del proyecto
| Archivo | Para qué sirve |
|---|---|
api_server.py |
Sirve la web dolar.anna.ar (cotización actual + histórico consultado en vivo a la API externa) |
index.html |
La página que se ve en el navegador |
requirements.txt |
Dependencias (flask, requests) |
instalar.sh |
Crea el venv e instala dependencias de una sola vez |
registrar_cotizacion.py |
Guarda la cotización del día en SQLite + exporta cotizacion_hoy.txt. Pensado para correr solo, una vez por día, con cron |
exportar_historico.py |
Genera un .txt con un rango de fechas (desde tu base propia, o completando con la API externa) |
dolar_historico.db |
Se crea sola la primera vez que corre registrar_cotizacion.py — tu propio histórico acumulado |
PARTE 1 — Instalación inicial (una sola vez)
1. Crear la carpeta y subir los archivos
sudo mkdir -p /opt/dolar
sudo chown -R $USER:$USER /opt/dolar
Subir por FileZilla a /opt/dolar/: api_server.py, index.html,
requirements.txt, instalar.sh, registrar_cotizacion.py,
exportar_historico.py.
2. Instalar entorno virtual y dependencias
cd /opt/dolar
source instalar.sh
(recordá: con source, no con ./instalar.sh — si no, la
activación del venv no queda en la terminal)
3. Instalar sqlite3 a nivel de servidor (una sola vez, para siempre)
Esto es el programa de consola para poder inspeccionar la base a
mano. La librería Python sqlite3 ya viene incluida en Python, esto
es aparte, solo para vos poder mirar los datos desde la terminal.
sudo apt install -y sqlite3
No hace falta repetir este paso para otros proyectos ni para
reinstalaciones — queda instalado en el sistema operativo, mismo
criterio que nginx o certbot.
4. Probar el servidor web a mano
python api_server.py
Probar en http://localhost:9091 (o con la IP pública si el puerto
ya está abierto). Cortar con Ctrl+C.
5. Armar el servicio systemd (para que la web quede corriendo sola)
sudo nano /etc/systemd/system/dolar.service
Contenido:
[Unit]
Description=Dolar Oficial (Flask)
After=network.target
[Service]
Type=simple
User=root
Group=root
WorkingDirectory=/opt/dolar
ExecStart=/opt/dolar/venv/bin/python api_server.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Activarlo:
sudo systemctl daemon-reload
sudo systemctl enable dolar.service
sudo systemctl start dolar.service
sudo systemctl status dolar.service
6. Subdominio + HTTPS
Mismo proceso que en tutorial_vps_flask.md, Parte C, con
dolar.anna.ar y puerto 9091. En resumen:
# DNS: registro A "dolar" -> IP de la VPS, cargado en Hostinger
sudo nano /etc/nginx/sites-available/dolar.anna.ar
server {
listen 80;
listen [::]:80;
server_name dolar.anna.ar;
location / {
proxy_pass http://127.0.0.1:9091;
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;
}
}
sudo ln -s /etc/nginx/sites-available/dolar.anna.ar /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d dolar.anna.ar
PARTE 2 — Registro automático diario (cron)
1. Probar el script a mano primero
cd /opt/dolar
source instalar.sh
python registrar_cotizacion.py
Tiene que mostrar algo como:
OK -> Compra: $XXXX.XX Venta: $XXXX.XX
Guardado en: /opt/dolar/dolar_historico.db
Exportado a: /opt/dolar/cotizacion_hoy.txt
2. Abrir el editor de tareas programadas
crontab -e
(la primera vez puede preguntar qué editor usar — elegir nano)
3. Agregar la tarea al final del archivo
0 9 * * * cd /opt/dolar && /opt/dolar/venv/bin/python /opt/dolar/registrar_cotizacion.py >> /opt/dolar/registro.log 2>&1
Guardar y salir: Ctrl+O, Enter, Ctrl+X.
Qué significa 0 9 * * *: "todos los días a las 9:00 AM"
(formato: minuto hora día mes día-semana, con * = "cualquiera").
Por qué la ruta completa al Python del venv: cron no ejecuta
source instalar.sh por nosotros, así que hay que decirle
explícitamente qué Python usar (/opt/dolar/venv/bin/python), no el
del sistema.
4. Confirmar que la tarea quedó cargada
crontab -l
5. Probar sin esperar al horario programado
Corriendo exactamente la misma línea a mano:
cd /opt/dolar && /opt/dolar/venv/bin/python /opt/dolar/registrar_cotizacion.py >> /opt/dolar/registro.log 2>&1
cat /opt/dolar/registro.log
Si el log muestra el mensaje de éxito, la tarea programada va a funcionar sola a partir de mañana.
6. Revisar el histórico acumulado (opcional, para confirmar)
sqlite3 /opt/dolar/dolar_historico.db "SELECT * FROM cotizaciones;"
PARTE 3 — Exportar un rango de fechas para PowerCOBOL
Cómo funciona
registrar_cotizacion.py(el del cron) va acumulando un registro por día, a partir de hoy en adelante — no trae retroactivamente los días anteriores a que empezaste a correrlo.exportar_historico.pygenera el.txtcon el rango que pidas, tomando esos datos acumulados.
Modo 1 — solo lo que ya tenés guardado (rápido)
python exportar_historico.py 2026-08-01 2026-08-30
Si el rango incluye fechas de antes de que arrancara el cron, va a traer menos registros de los esperados (solo los días que ya se guardaron solos). No es un error, es esperable.
Modo 2 — completando huecos con la API externa (para el pasado)
python exportar_historico.py --completar 2026-08-01 2026-08-30
Este modo, además de exportar, completa tu base SQLite con los
días que falten (consultándolos a ArgentinaDatos), así la próxima vez
que pidas ese mismo rango, ya no hace falta --completar de nuevo.
Resultado
Se genera un archivo historico_DESDE_a_HASTA.txt en /opt/dolar/,
con formato:
fecha;compra;venta
2026-08-01;1470.00;1490.00
2026-08-15;1478.00;1498.00
2026-08-29;1485.00;1535.00
Listo para bajarlo por FileZilla y leerlo desde PowerCOBOL.
Resumen — checklist rápido
Instalación (una vez):
1. mkdir /opt/dolar + permisos
2. Subir los 6 archivos
3. source instalar.sh
4. sudo apt install -y sqlite3 (una sola vez por servidor)
5. Probar python api_server.py a mano
6. Armar dolar.service, activar
7. Subdominio + nginx + Certbot
Uso diario (automático, no requiere nada de vos):
- cron corre registrar_cotizacion.py todos los días a las 9 AM
Cuando necesites un rango para PowerCOBOL:
cd /opt/dolar && source instalar.sh
python exportar_historico.py [--completar] DESDE HASTA
Comandos de diagnóstico útiles:
crontab -l # ver tareas programadas
cat /opt/dolar/registro.log # ver el log del cron
sqlite3 /opt/dolar/dolar_historico.db "SELECT * FROM cotizaciones;" # ver la base
sudo systemctl status dolar.service # ver si la web sigue viva
Tutorial: sitio PHP + MySQL/MariaDB en la VPS
Guía de referencia para desplegar un sitio en PHP con base de datos, distinta a la receta de Flask porque acá no hay venv ni systemd por proyecto — PHP-FPM y MariaDB son servicios únicos, compartidos por todos los proyectos PHP que armes.
Diferencia clave con los proyectos Flask (agro, dolar, etc.)
| Flask (Python) | PHP | |
|---|---|---|
| ¿Necesita venv? | Sí, uno por proyecto | No |
¿Necesita .service propio? |
Sí, uno por proyecto | No — PHP-FPM ya está corriendo para todos |
| ¿Dónde vive el proyecto? | /opt/nombre |
/var/www/nombre |
| ¿Qué hace nginx? | Reenvía (proxy_pass) a un puerto Python aparte |
Sirve los archivos directo, y delega los .php a PHP-FPM |
| Base de datos | La que vos armes (SQLite, etc.) | MariaDB, instalado una vez para todo el servidor |
PARTE 1 — Instalación de PHP-FPM y MariaDB (una sola vez por servidor)
1. Instalar PHP-FPM y extensiones comunes
sudo apt update
sudo apt install -y php-fpm php-mysql php-mbstring php-xml php-curl
Confirmar la versión real instalada (importante para el paso de nginx más adelante):
sudo systemctl status php8.3-fpm
(el número puede variar; usar el que confirme el status)
2. Instalar MariaDB
sudo apt install -y mariadb-server
sudo systemctl status mariadb
3. Asegurar la instalación
sudo mysql_secure_installation
Preguntas y respuestas: - "Enter current password for root" → Enter en blanco (recién instalado, no tiene contraseña todavía) - "Set root password?" → Y, y ahí sí escribir una contraseña real - Remove anonymous users? → Y - Disallow root login remotely? → Y - Remove test database? → Y - Reload privilege tables now? → Y
⚠️ Guardar la contraseña de root de MariaDB en un lugar seguro (gestor de contraseñas, no en el chat ni en ningún archivo del servidor).
PARTE 2 — Crear un proyecto nuevo (repetible por cada sitio PHP)
1. Crear la base de datos y un usuario propio del proyecto
sudo mysql -u root -p
Dentro de la consola:
CREATE DATABASE nombreproyecto;
CREATE USER 'nombreproyecto_user'@'localhost' IDENTIFIED BY 'CONTRASEÑA_PROPIA_DISTINTA_DE_ROOT';
GRANT ALL PRIVILEGES ON nombreproyecto.* TO 'nombreproyecto_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
⚠️ Nunca usar la misma contraseña que la de root. Si esta contraseña de proyecto se filtra alguna vez (por ejemplo, quedó escrita en un archivo PHP que se compartió sin querer), con contraseñas distintas solo se compromete esa base puntual, no el servidor de bases de datos entero.
Para generar una contraseña segura al azar, se puede pedir en el momento en vez de inventar una de memoria.
2. Crear la carpeta del proyecto
A diferencia de /opt/, PHP usa la convención /var/www/, donde
nginx y PHP-FPM ya tienen los permisos correctos para el usuario
www-data (el que ejecuta ambos procesos):
sudo mkdir -p /var/www/nombreproyecto
sudo chown -R www-data:www-data /var/www/nombreproyecto
3. Crear el archivo de prueba
sudo nano /var/www/nombreproyecto/index.php
<?php
echo "<h1>PHP funcionando</h1>";
echo "<p>Versión: " . phpversion() . "</p>";
$conexion = new mysqli("localhost", "nombreproyecto_user", "CONTRASEÑA_PROPIA", "nombreproyecto");
if ($conexion->connect_error) {
echo "<p style='color:red'>Error de conexión a la base: " . $conexion->connect_error . "</p>";
} else {
echo "<p style='color:green'>Conexión a la base de datos exitosa.</p>";
}
$conexion->close();
?>
Guardar y salir: Ctrl+O, Enter, Ctrl+X.
4. Crear la configuración de nginx
sudo nano /etc/nginx/sites-available/nombreproyecto.anna.ar
server {
listen 80;
listen [::]:80;
server_name nombreproyecto.anna.ar;
root /var/www/nombreproyecto;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
(ajustar php8.3-fpm.sock al número de versión real confirmado en el Paso 1)
5. Activar el sitio
sudo ln -s /etc/nginx/sites-available/nombreproyecto.anna.ar /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
6. DNS + HTTPS
Mismo proceso que con los proyectos Flask (ver
tutorial_vps_flask.md, Parte C):
- Registro A del subdominio en Hostinger, apuntando a la IP de la VPS.
- Confirmar propagación con
nslookup nombreproyecto.anna.ar. - Probar por HTTP:
http://nombreproyecto.anna.ar. - Abrir puerto 80/443 en
ufwy en el panel de Contabo (si es la primera vez en esta VPS; si ya se abrieron para otro proyecto, no hace falta repetirlo). - Generar el certificado:
bash sudo certbot --nginx -d nombreproyecto.anna.ar - Probar por HTTPS:
https://nombreproyecto.anna.ar.
Errores comunes vistos al armar ayuda.anna.ar
"El archivo de configuración quedó con el nombre viejo del proyecto"
Si en algún momento se renombra el proyecto (por ejemplo, de
miweb a ayuda), hay que renombrar tres cosas por separado,
ninguna se actualiza sola:
sudo mv /etc/nginx/sites-available/NOMBRE_VIEJO /etc/nginx/sites-available/NOMBRE_NUEVO
sudo mv /var/www/NOMBRE_VIEJO /var/www/NOMBRE_NUEVO
Y dentro del archivo de nginx, actualizar a mano server_name y
root con el nombre nuevo. También borrar el link viejo de
sites-enabled si ya se había activado, y volver a crearlo con el
nombre correcto.
"La página de prueba no muestra el mensaje de conexión a la base"
Puede ser que el index.php que quedó en el servidor sea una
versión más corta (por ejemplo, si se creó antes de agregar el
bloque de mysqli). Revisar con cat /var/www/nombreproyecto/index.php
y completar si falta.
PARTE 3 — Administrar la base de datos visualmente (sin panel = incómodo)
Trabajar la base solo por consola SQL es tedioso. Hay opciones para verla y editarla con una interfaz web o de escritorio.
Opción rápida: Adminer (un solo archivo PHP)
cd /var/www/nombreproyecto
sudo wget https://www.adminer.org/latest.php -O adminer.php
Probar en https://nombreproyecto.anna.ar/adminer.php.
Datos de login — ojo con no mezclar usuarios:
| Campo | Con el usuario del proyecto | Con root |
|---|---|---|
| Servidor | localhost |
localhost |
| Usuario | nombreproyecto_user |
root |
| Contraseña | la de nombreproyecto_user |
la de root (la de mysql_secure_installation) |
| Base de datos | nombreproyecto |
(dejar vacío, muestra todas) |
⚠️ Error común: usar el usuario root con la contraseña del
proyecto (o viceversa) — son dos usuarios completamente separados,
cada uno con su propia contraseña. No se pueden combinar.
Truco para no olvidar las credenciales: el propio archivo
index.php de prueba (Parte 2, Paso 3) ya tiene las credenciales
reales escritas en la línea de new mysqli(...) — sirve como
referencia rápida de qué usuario/contraseña/base usar en cualquier
herramienta.
⚠️ Adminer queda público en internet una vez subido. Para uso esporádico, subirlo cuando se necesita y borrarlo después:
sudo rm /var/www/nombreproyecto/adminer.php
Para dejarlo instalado de forma permanente y segura, agregarle autenticación básica a nivel de nginx (contraseña extra antes de llegar al login de Adminer).
Opción más prolija: cliente de escritorio (HeidiSQL, DBeaver, TablePlus)
Ventaja: no expone nada nuevo a internet, interfaz más cómoda para
uso frecuente. Como MariaDB está configurado para escuchar solo en
localhost (no accesible directo desde afuera, por seguridad), la
conexión desde estos programas se hace mediante un túnel SSH
(la mayoría de estos clientes lo tienen integrado como una pestaña
más de configuración, no hace falta armarlo aparte a mano).
Una sola vez por servidor:
1. apt install php-fpm php-mysql ...
2. apt install mariadb-server
3. mysql_secure_installation
Por cada proyecto PHP nuevo:
1. Crear base + usuario propio (contraseña distinta a root)
2. mkdir /var/www/nombreproyecto + permisos a www-data
3. Crear index.php de prueba con conexión a la base
4. Archivo de nginx (root + fastcgi_pass al socket de PHP-FPM correcto)
5. Activar (ln -s + nginx -t + reload)
6. DNS + HTTP + Certbot + HTTPS (igual que en Flask)