IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Nginx Log Rotation and App Log Debugging on Ubuntu 24.04

By Raman Kumar

Share:

Updated on Sep 4, 2026

Nginx Log Rotation and App Log Debugging on Ubuntu 24.04

Why this setup matters on a live VPS

When Nginx logs fill a small VPS, the first problem is often not the website. It is disk pressure, missing access logs, or a quiet app error that only appears after a reload. This tutorial shows you how to fix Nginx log rotation and app log debugging on Hostperl VPS hosting using Ubuntu Server 24.04, with safe sequencing, verification, and a rollback path.

You will create a non-root admin, inspect the server, confirm log rotation, and connect Nginx to a simple Python app so you can trace one request end to end. If you already run production sites, this is the kind of maintenance that prevents late-night support tickets and keeps the evidence intact after an outage.

What you are building

The goal is straightforward: keep Nginx logs readable, rotate them before they grow too large, and make app errors easy to trace in the same incident window. The stack here is Ubuntu 24.04, Nginx, systemd, logrotate, and a small Python app behind Nginx on port 8000.

If you want to stage this on a fresh VPS before moving it into production, Hostperl’s managed VPS hosting is a solid fit for single-site launches, agency work, and customer migrations where you need quick access to logs and predictable support.

  • Primary focus: Nginx log rotation
  • OS: Ubuntu Server 24.04
  • Outcome: Rotated access/error logs, app log capture, and a testable debug workflow

1) Connect to the VPS and confirm the OS

On your local computer

ssh root@203.0.113.10

203.0.113.10 is a reserved documentation address. Replace it with the real public IP assigned by your hosting provider.

On the VPS as root

cat /etc/os-release

You should see Ubuntu 24.04 in the output. If you are not on Ubuntu 24.04, stop here and adapt the package and service names for your distribution before proceeding.

2) Create a sudo user and keep root open

Do not close the root SSH session yet. Create a second admin account first, then test it from a second terminal.

On the VPS as root

adduser deploy

Set a strong password when prompted. Then grant sudo access:

usermod -aG sudo deploy

Prepare SSH access for the new user:

mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

If your root account does not already use SSH keys, copy your public key from your local machine instead. The key point is to verify the deploy login before changing root access.

On your local computer

ssh deploy@203.0.113.10

Open a second terminal and confirm sudo works:

sudo -v

If this succeeds, keep both sessions open. That is your rollback safety net.

3) Update packages and install the logging tools

On the VPS as the non-root sudo user

sudo apt update
sudo apt -y upgrade
sudo apt -y install nginx python3 python3-venv logrotate curl

Ubuntu 24.04 includes current Nginx and logrotate packages in the standard repositories. After installation, check versions:

nginx -v
logrotate --version

You should see both tools installed and available. If package installation fails, inspect the apt cache and your network path. For general VPS planning and configuration guidance, Hostperl’s VPS hosting options are designed for small business sites, developer apps, and recovery drills like this one.

4) Create a small app that writes useful logs

To debug logs properly, you need a real app behind Nginx. This Python service returns one line to the browser and writes request details to a separate application log.

On the VPS as the non-root sudo user

sudo mkdir -p /opt/myapp
sudo chown -R deploy:deploy /opt/myapp
cd /opt/myapp
python3 -m venv .venv
. .venv/bin/activate
pip install --upgrade pip

Create the app file:

cat > /opt/myapp/app.py <<'EOF'
from flask import Flask, request
import logging

app = Flask(__name__)
logging.basicConfig(filename='/opt/myapp/app.log', level=logging.INFO,
                    format='%(asctime)s %(levelname)s %(message)s')

@app.get('/')
def index():
    logging.info('request from %s path=%s', request.remote_addr, request.path)
    return 'OK from app\n'

@app.get('/error')
def error():
    logging.exception('forced error for debugging')
    raise RuntimeError('forced error')
EOF

Install Flask and run a quick foreground test:

pip install flask gunicorn
/opt/myapp/.venv/bin/gunicorn --bind 127.0.0.1:8000 app:app

Stop it with Ctrl+C after you see Gunicorn start cleanly. Then create the systemd service so the app survives reboots.

5) Run the app as a service

On the VPS as root

cat > /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=My App behind Nginx
After=network.target

[Service]
User=deploy
Group=deploy
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/.venv/bin"
ExecStart=/opt/myapp/.venv/bin/gunicorn --bind 127.0.0.1:8000 app:app
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now myapp.service
systemctl status --no-pager myapp.service

You want the service to show active (running). If it fails, inspect the journal with journalctl -u myapp.service -n 50 --no-pager.

6) Configure Nginx and keep logs readable

This is the core of the tutorial. Nginx will proxy requests to the app and write access and error logs to dedicated files so rotation stays clean.

On the VPS as root

cat > /etc/nginx/sites-available/myapp <<'EOF'
server {
    listen 80;
    server_name server.example.com;

    access_log /var/log/nginx/myapp-access.log;
    error_log /var/log/nginx/myapp-error.log warn;

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}
EOF
ln -sfn /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
rm -f /etc/nginx/sites-enabled/default
nginx -t

server.example.com is a documentation hostname. Replace it with your real hostname before loading production traffic.

Now load Nginx:

systemctl enable --now nginx
systemctl reload nginx

Check that Nginx is listening and the app is reachable:

ss -tulpn | grep ':80'
curl -i http://127.0.0.1/

You should get OK from app back from the proxy. If you do not, inspect /var/log/nginx/myapp-error.log and the systemd journal for the app service.

7) Add logrotate for Nginx and app logs

Ubuntu already ships logrotate, but your app log needs its own policy. The Nginx package also relies on a rotation job, so you should check both.

On the VPS as root

cat > /etc/logrotate.d/myapp <<'EOF'
/var/log/nginx/myapp-access.log /var/log/nginx/myapp-error.log /opt/myapp/app.log {
    daily
    rotate 14
    missingok
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        systemctl reload nginx >/dev/null 2>&1 || true
    endscript
}
EOF
logrotate -d /etc/logrotate.d/myapp

The -d flag performs a dry run. If the syntax is wrong, fix the file before forcing a real rotation. This protects you from losing logs during a bad edit.

Run a controlled test rotation:

logrotate -f /etc/logrotate.d/myapp
ls -lh /var/log/nginx/myapp-*
ls -lh /opt/myapp/app.log*

You should see compressed or newly created log files. That confirms the rotation policy is working.

8) Generate errors and trace them end to end

Good logging only helps if you can prove it during an incident. Hit the normal path, then the error path:

On your local computer

curl -i http://203.0.113.10/
curl -i http://203.0.113.10/error

The first request should return 200 OK. The second should produce a 500 because the app raises a forced exception. That is expected.

On the VPS as the non-root sudo user

sudo tail -n 20 /var/log/nginx/myapp-access.log
sudo tail -n 20 /var/log/nginx/myapp-error.log
sudo tail -n 20 /opt/myapp/app.log
sudo journalctl -u myapp.service -n 20 --no-pager

You should see the request path in Nginx access logs and the forced exception in the application log. For teams that run multiple app tiers, this is the same workflow you would use before a migration or cutover. Hostperl also documents related operations in Docker deployment checklist for safer launches and blue-green deployment procedures when the app stack is containerized.

9) Harden the logs and the firewall

Ubuntu 24.04 usually ships with UFW available. Open only what you need.

On the VPS as root

ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable
ufw status verbose

Only enable UFW after you have confirmed SSH access from the second terminal. That avoids locking yourself out.

If you want to reduce root access later, do it after you have verified the deploy login and your service restarts cleanly after a reboot:

systemctl reboot

Then reconnect as deploy and confirm the app and logs still work.

10) Troubleshooting the most likely failures

Nginx reload fails with a syntax error

Diagnostic:

nginx -t

Expected clue: line number and file path, usually in /etc/nginx/sites-available/myapp. Fix the file, then run nginx -t again before reloading.

App returns 502 Bad Gateway

Diagnostic:

systemctl status --no-pager myapp.service
sudo journalctl -u myapp.service -n 50 --no-pager
ss -tulpn | grep ':8000'

If the app is not listening on 127.0.0.1:8000, restart the service after checking the ExecStart path and virtual environment.

Logs stop rotating

Diagnostic:

logrotate -d /etc/logrotate.d/myapp
sudo ls -l /etc/logrotate.d/myapp
sudo tail -n 20 /var/log/syslog

Expected clue: a bad path, missing permissions, or a broken postrotate command. Correct the file, then force a test rotation again.

Rollback and recovery

If this change causes trouble, revert in the safest order. First, disable the custom site, then stop the app service, then restore the default Nginx site if needed.

On the VPS as root

rm -f /etc/nginx/sites-enabled/myapp
ln -sfn /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginx
systemctl disable --now myapp.service

That brings the server back to a simple web state while you inspect the app and logs. Keep the original root session until the rollback is confirmed.

Final verification

Finish with a clean test from both sides.

On the VPS as the non-root sudo user

systemctl is-enabled nginx myapp.service
systemctl is-active nginx myapp.service
sudo tail -n 5 /var/log/nginx/myapp-access.log
sudo tail -n 5 /opt/myapp/app.log

On your local computer

curl -I http://203.0.113.10/
curl http://203.0.113.10/

If both commands return the expected headers and body, your Nginx log rotation and app log debugging setup is working. This is a practical fit for launch-ready sites, agency maintenance windows, and customers who want straightforward support on a reliable Hostperl VPS.

If you want this kind of logging discipline on a production server, Hostperl can help you size the VPS correctly and keep the stack supportable. A well-chosen VPS hosting plan gives you enough headroom for rotation, logs, and recovery tests without crowding the filesystem.

For teams that want fewer surprises during customer launches, Hostperl’s support and migration workflow make it easier to keep Nginx, app services, and backups in sync.

FAQ

Does this work for Debian too?
Yes, the same idea works on Debian-family systems, but package and service names should be checked on that release before applying the commands.

Why use a separate app log instead of only Nginx logs?
Because Nginx shows the request path and status, while the app log shows stack traces and application-level failures. You need both during an incident.

How often should I rotate logs?
Daily rotation with 14 compressed copies is a good starting point for busy small and medium sites. Increase retention if your support workflow depends on longer history.

What is the fastest health check after a reboot?
Run systemctl is-active nginx myapp.service, then curl http://127.0.0.1/. That confirms both the proxy and the app came back cleanly.

Can I use this with PHP, Node.js, or Java?
Yes. The logging and rotation approach is the same. Only the upstream service and runtime-specific log path change.