Skip to main content

Overview

Deploying Simple Invoice to production requires careful attention to security, performance, and reliability. This guide covers essential configuration and hardening steps.

Pre-Deployment Checklist

Before deploying to production:
  • Change default database credentials
  • Secure config/db.php file permissions
  • Configure SSL/HTTPS
  • Set up automated backups
  • Configure PHP upload limits
  • Disable error display
  • Import database schema
  • Test file upload functionality

Security Configuration

1. Database Credentials

Critical: The default configuration uses root with an empty password. This must be changed before production deployment.
Update config/db.php with secure credentials:
<?php
/*Datos de conexion a la base de datos*/
define('DB_HOST', 'localhost');
define('DB_USER', 'invoice_user');  // Change from 'root'
define('DB_PASS', 'strong_secure_password_here');  // Set a strong password
define('DB_NAME', 'simple_invoice');
?>
Password requirements:
  • Minimum 16 characters
  • Mix of uppercase, lowercase, numbers, and symbols
  • No dictionary words
  • Unique to this application

2. File Permissions

Set restrictive permissions on sensitive files:
# Configuration files - read-only for web server
chmod 640 config/db.php
chmod 640 config/conexion.php
chown www-data:www-data config/*.php

# Upload directories - writable
chmod 755 img/
chmod 755 pdf/documentos/
chmod 755 pdf/documentos/res/

# Application files - read-only
find . -type f -name "*.php" ! -path "./config/*" ! -path "./img/*" ! -path "./pdf/documentos/*" -exec chmod 644 {} \;
The application requires write access to /img for logo uploads and /pdf/documentos for invoice PDF generation.

3. Database Security

1

Create dedicated database user

CREATE USER 'invoice_user'@'localhost' IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON simple_invoice.* TO 'invoice_user'@'localhost';
FLUSH PRIVILEGES;
2

Import database schema

mysql -u invoice_user -p simple_invoice < simple_invoice.sql
3

Remove root access

-- Verify tables were created
USE simple_invoice;
SHOW TABLES;

-- Tables: clientes, currencies, detalle_factura, facturas,
--         perfil, productos, users
4

Secure MySQL configuration

Add to /etc/mysql/my.cnf:
[mysqld]
local-infile=0
skip-show-database
bind-address=127.0.0.1

4. SSL/HTTPS Setup

Required for production: Always use HTTPS to protect user credentials and sensitive invoice data.

Apache Configuration

<VirtualHost *:443>
    ServerName invoice.yourdomain.com
    DocumentRoot /var/www/simple-invoice
    
    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/invoice.crt
    SSLCertificateKeyFile /etc/ssl/private/invoice.key
    SSLCertificateChainFile /etc/ssl/certs/chain.pem
    
    # Security headers
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    
    <Directory /var/www/simple-invoice>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

# Redirect HTTP to HTTPS
<VirtualHost *:80>
    ServerName invoice.yourdomain.com
    Redirect permanent / https://invoice.yourdomain.com/
</VirtualHost>

Nginx Configuration

server {
    listen 443 ssl http2;
    server_name invoice.yourdomain.com;
    root /var/www/simple-invoice;
    index index.php;
    
    ssl_certificate /etc/ssl/certs/invoice.crt;
    ssl_certificate_key /etc/ssl/private/invoice.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    # Deny access to sensitive files
    location ~ /config/ {
        deny all;
        return 404;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name invoice.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

5. PHP Configuration

Upload Limits

The application uploads logos to /img with a 1MB limit (see ajax/imagen_ajax.php:19). Configure PHP accordingly:
; /etc/php/5.6/apache2/php.ini (or php-fpm/php.ini for Nginx)

upload_max_filesize = 2M
post_max_size = 3M
max_execution_time = 30
memory_limit = 128M

; File upload security
file_uploads = On
allow_url_fopen = Off
allow_url_include = Off
Logo uploads support JPG, JPEG, PNG, and GIF formats. The validation is at ajax/imagen_ajax.php:17.

Error Handling

Production settings:
; Display errors - DISABLED for production
display_errors = Off
display_startup_errors = Off

; Log errors instead
log_errors = On
error_log = /var/log/php/error.log

; Error reporting level
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
Create log directory:
mkdir -p /var/log/php
chown www-data:www-data /var/log/php
chmod 755 /var/log/php

6. Application Hardening

Disable Directory Listing

The application includes index.php files in most directories, but add this to your web server config: Apache:
Options -Indexes
Nginx:
autoindex off;

Protect Configuration Files

Apache - Create .htaccess in /config:
Order deny,allow
Deny from all
Nginx - Already configured in the example above:
location ~ /config/ {
    deny all;
    return 404;
}

Backup Strategies

Database Backups

1

Create backup script

#!/bin/bash
# /usr/local/bin/backup-invoice-db.sh

BACKUP_DIR="/var/backups/simple-invoice"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="simple_invoice"
DB_USER="invoice_user"
DB_PASS="your_password"

mkdir -p $BACKUP_DIR

mysqldump -u $DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_DIR/db_$DATE.sql.gz

# Keep last 30 days
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +30 -delete
2

Schedule with cron

# Daily at 2 AM
0 2 * * * /usr/local/bin/backup-invoice-db.sh
3

Test restoration

# Restore from backup
gunzip < /var/backups/simple-invoice/db_20260305_020000.sql.gz | mysql -u invoice_user -p simple_invoice

File Backups

Backup uploaded files and PDFs:
#!/bin/bash
# /usr/local/bin/backup-invoice-files.sh

BACKUP_DIR="/var/backups/simple-invoice"
APP_DIR="/var/www/simple-invoice"
DATE=$(date +%Y%m%d_%H%M%S)

tar -czf $BACKUP_DIR/files_$DATE.tar.gz \
  $APP_DIR/img \
  $APP_DIR/pdf/documentos \
  $APP_DIR/config

# Keep last 30 days
find $BACKUP_DIR -name "files_*.tar.gz" -mtime +30 -delete

Performance Optimization

PHP OpCache

Enable OpCache for better performance:
; /etc/php/5.6/apache2/conf.d/10-opcache.ini

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1

Database Optimization

-- Analyze tables
ANALYZE TABLE clientes, facturas, productos, detalle_factura;

-- Optimize tables
OPTIMIZE TABLE clientes, facturas, productos, detalle_factura;

-- Add indexes for common queries
CREATE INDEX idx_status ON clientes(status_cliente);
CREATE INDEX idx_date ON facturas(date_added);

Web Server Caching

Apache:
# Enable compression
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript
</IfModule>

# Browser caching
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
</IfModule>

Monitoring

Error Monitoring

Monitor PHP error logs:
# Real-time monitoring
tail -f /var/log/php/error.log

# Alert on errors
grep -i "fatal\|error" /var/log/php/error.log | mail -s "Invoice Errors" [email protected]

Database Monitoring

-- Check database size
SELECT 
    table_name AS 'Table',
    ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size (MB)'
FROM information_schema.TABLES
WHERE table_schema = 'simple_invoice'
ORDER BY (data_length + index_length) DESC;

-- Check connection count
SHOW STATUS WHERE Variable_name = 'Threads_connected';

Disk Space Monitoring

# Monitor upload directories
du -sh /var/www/simple-invoice/img
du -sh /var/www/simple-invoice/pdf/documentos

Update Procedures

1

Backup current installation

tar -czf simple-invoice-backup-$(date +%Y%m%d).tar.gz /var/www/simple-invoice
2

Enable maintenance mode

Create a temporary maintenance page or use web server redirect.
3

Update application files

cd /var/www/simple-invoice
git pull origin main
# Or extract new version archive
4

Preserve configuration

# Ensure config/db.php is not overwritten
git update-index --assume-unchanged config/db.php
5

Update database schema

mysql -u invoice_user -p simple_invoice < migrations/update.sql
6

Clear OpCache

service apache2 reload
# Or for PHP-FPM:
service php5.6-fpm reload
7

Test and restore access

Verify the application is working, then disable maintenance mode.

Troubleshooting

Database Connection Errors

Check config/conexion.php:8-14 for connection logic. Common issues:
# Test MySQL connection
mysql -u invoice_user -p -h localhost simple_invoice

# Check MySQL is running
systemctl status mysql

# Verify credentials match config/db.php
grep -E "DB_USER|DB_PASS|DB_NAME" config/db.php

Upload Failures

Logo upload validation at ajax/imagen_ajax.php:17-20:
# Check directory permissions
ls -la img/

# Verify PHP upload settings
php -i | grep -E "upload_max_filesize|post_max_size"

# Check web server error logs
tail -f /var/log/apache2/error.log  # Apache
tail -f /var/log/nginx/error.log    # Nginx

Permission Issues

# Fix ownership
chown -R www-data:www-data /var/www/simple-invoice

# Fix permissions
find /var/www/simple-invoice -type d -exec chmod 755 {} \;
find /var/www/simple-invoice -type f -exec chmod 644 {} \;
chmod 755 /var/www/simple-invoice/img
chmod 755 /var/www/simple-invoice/pdf/documentos

Security Checklist

Before going live:
  • Changed default database credentials in config/db.php
  • Set chmod 640 on config/db.php
  • Disabled PHP display_errors
  • Enabled HTTPS/SSL
  • Configured security headers
  • Set up automated backups
  • Tested backup restoration
  • Configured firewall rules
  • Disabled directory listing
  • Protected /config directory
  • Set correct file permissions
  • Enabled OpCache
  • Configured error logging
  • Tested file uploads (1MB limit)
  • Reviewed PHP upload settings

Next Steps

  • Set up monitoring and alerts
  • Configure fail2ban for brute force protection
  • Implement regular security audits
  • Plan disaster recovery procedures
  • Document your specific deployment configuration

Build docs developers (and LLMs) love