Invoice Ninja Installation Guide

Invoice Ninja is a powerful open-source invoicing and billing platform designed for freelancers and businesses. It provides features for creating invoices, quotes, managing clients, tracking payments, time tracking, and expense management.

Business applications🟡 intermediate9 min⏱️ 15-30 minutes

Invoice Ninja is a powerful open-source invoicing and billing platform designed for freelancers and businesses. It provides features for creating invoices, quotes, managing clients, tracking payments, time tracking, and expense management.

1. Prerequisites

  • Linux-based operating system (Ubuntu 20.04+ or Debian 11+ recommended)
  • Minimum 2GB RAM (4GB recommended for production)
  • 10GB disk space minimum
  • Domain name with DNS configured
  • SSL certificate (Let's Encrypt recommended)
  • PHP 8.1+ with required extensions
  • MySQL 5.7+ or MariaDB 10.3+
  • Redis for caching and queues
  • nginx or Apache web server
  • 2. Supported Operating Systems

    This guide supports installation on:

  • RHEL 8/9 and derivatives (CentOS Stream, Rocky Linux, AlmaLinux)
  • Debian 11/12
  • Ubuntu 20.04/22.04/24.04 LTS
  • Arch Linux (rolling release)
  • Alpine Linux 3.18+
  • openSUSE Leap 15.5+ / Tumbleweed
  • SUSE Linux Enterprise Server (SLES) 15+
  • macOS 12+ (Monterey and later)
  • FreeBSD 13+
  • Windows 10/11/Server 2019+ (where applicable)
  • 3. Installation

    1. Create directory structure:

    bash
    # Create base directory
    sudo mkdir -p /opt/invoiceninja
    cd /opt/invoiceninja
    
    # Create required directories
    mkdir -p docker/app/public docker/app/storage nginx/conf.d

    2. Create docker-compose.yml:

    yaml
    version: '3.7'
    
    services:
      nginx:
        image: nginx:alpine
        restart: always
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - ./nginx/conf.d:/etc/nginx/conf.d:ro
          - ./docker/app/public:/var/www/app/public:ro
          - ./ssl:/etc/nginx/ssl:ro
        depends_on:
          - app
        networks:
          - invoiceninja
    
      app:
        image: invoiceninja/invoiceninja:5
        restart: always
        env_file: .env
        volumes:
          - ./docker/app/public:/var/www/app/public
          - ./docker/app/storage:/var/www/app/storage
        depends_on:
          - db
          - redis
        networks:
          - invoiceninja
    
      db:
        image: mariadb:10.6
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
          MYSQL_DATABASE: ${MYSQL_DATABASE}
          MYSQL_USER: ${MYSQL_USER}
          MYSQL_PASSWORD: ${MYSQL_PASSWORD}
        volumes:
          - db_data:/var/lib/mysql
        networks:
          - invoiceninja
    
      redis:
        image: redis:alpine
        restart: always
        volumes:
          - redis_data:/data
        networks:
          - invoiceninja
    
    volumes:
      db_data:
      redis_data:
    
    networks:
      invoiceninja:
        driver: bridge

    3. Create environment file:

    bash
    # Create .env file
    cat > .env << 'EOF'
    # Application
    APP_ENV=production
    APP_DEBUG=false
    APP_URL=https://invoice.example.com
    APP_KEY=base64:$(openssl rand -base64 32)
    APP_CIPHER=AES-256-CBC
    
    # Database
    DB_CONNECTION=mysql
    DB_HOST=db
    DB_PORT=3306
    DB_DATABASE=invoiceninja
    DB_USERNAME=invoiceninja
    DB_PASSWORD=$(openssl rand -base64 32)
    
    # MySQL Root
    MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32)
    MYSQL_DATABASE=invoiceninja
    MYSQL_USER=invoiceninja
    MYSQL_PASSWORD=${DB_PASSWORD}
    
    # Cache and Queue
    CACHE_DRIVER=redis
    SESSION_DRIVER=redis
    QUEUE_CONNECTION=redis
    REDIS_HOST=redis
    REDIS_PASSWORD=null
    REDIS_PORT=6379
    
    # Mail
    MAIL_MAILER=smtp
    MAIL_HOST=smtp.gmail.com
    MAIL_PORT=587
    MAIL_USERNAME=your-email@gmail.com
    MAIL_PASSWORD=your-app-password
    MAIL_ENCRYPTION=tls
    MAIL_FROM_ADDRESS=noreply@invoice.example.com
    MAIL_FROM_NAME="Invoice Ninja"
    
    # Additional settings
    REQUIRE_HTTPS=true
    TRUSTED_PROXIES="*"
    PDF_GENERATOR=snappdf
    PHANTOMJS_PDF_GENERATION=false
    EOF

    4. Configure nginx:

    bash
    # Create nginx configuration
    cat > nginx/conf.d/invoiceninja.conf << 'EOF'
    server {
        listen 80;
        server_name invoice.example.com;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name invoice.example.com;
        
        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        
        root /var/www/app/public;
        index index.php;
        
        client_max_body_size 50M;
        
        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }
        
        location ~ \.php$ {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass app:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_buffer_size 32k;
            fastcgi_buffers 8 16k;
        }
        
        location ~ /\.ht {
            deny all;
        }
    }
    EOF

    5. Start Invoice Ninja:

    bash
    # Start services
    docker-compose up -d
    
    # Run migrations
    docker-compose exec app php artisan migrate:fresh --seed
    
    # Generate app key (if not set)
    docker-compose exec app php artisan key:generate
    
    # Optimize application
    docker-compose exec app php artisan optimize

    Manual Installation

    1. Install system dependencies:

    bash
    # Ubuntu/Debian
    sudo apt update
    sudo apt install -y \
      nginx \
      mariadb-server \
      redis-server \
      php8.1-fpm \
      php8.1-mysql \
      php8.1-curl \
      php8.1-gd \
      php8.1-mbstring \
      php8.1-xml \
      php8.1-zip \
      php8.1-bcmath \
      php8.1-intl \
      php8.1-redis \
      php8.1-imagick \
      php8.1-gmp \
      composer \
      git \
      curl \
      unzip

    2. Create database:

    bash
    # Secure MariaDB
    sudo mysql_secure_installation
    
    # Create database and user
    sudo mysql -u root -p << EOF
    CREATE DATABASE invoiceninja;
    CREATE USER 'invoiceninja'@'localhost' IDENTIFIED BY 'secure_password';
    GRANT ALL PRIVILEGES ON invoiceninja.* TO 'invoiceninja'@'localhost';
    FLUSH PRIVILEGES;
    EXIT;
    EOF

    3. Download Invoice Ninja:

    bash
    # Create directory
    sudo mkdir -p /var/www/invoiceninja
    cd /var/www/invoiceninja
    
    # Download latest release
    wget https://github.com/invoiceninja/invoiceninja/releases/latest/download/invoiceninja.zip
    unzip invoiceninja.zip
    rm invoiceninja.zip
    
    # Set permissions
    sudo chown -R www-data:www-data /var/www/invoiceninja
    sudo chmod -R 755 /var/www/invoiceninja
    sudo chmod -R 775 /var/www/invoiceninja/storage
    sudo chmod -R 775 /var/www/invoiceninja/bootstrap/cache

    4. Configure application:

    bash
    # Copy environment file
    cp .env.example .env
    
    # Edit configuration
    nano .env
    
    # Generate application key
    php artisan key:generate
    
    # Run migrations
    php artisan migrate:fresh --seed
    
    # Optimize application
    php artisan optimize

    4. Configuration

    Nginx Configuration

    Create /etc/nginx/sites-available/invoiceninja:

    nginx
    server {
        listen 80;
        server_name invoice.example.com;
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name invoice.example.com;
        
        ssl_certificate /etc/letsencrypt/live/invoice.example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/invoice.example.com/privkey.pem;
        
        root /var/www/invoiceninja/public;
        index index.php;
        
        client_max_body_size 50M;
        
        # Security headers
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header Referrer-Policy "no-referrer-when-downgrade" always;
        
        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }
        
        location ~ \.php$ {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_intercept_errors off;
            fastcgi_buffer_size 16k;
            fastcgi_buffers 4 16k;
        }
        
        location ~ /\.ht {
            deny all;
        }
        
        location ~ /\.(?!well-known).* {
            deny all;
        }
    }

    Enable site:

    bash
    sudo ln -s /etc/nginx/sites-available/invoiceninja /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl reload nginx

    Queue Worker

    Create /etc/systemd/system/invoiceninja-queue.service:

    ini
    [Unit]
    Description=Invoice Ninja Queue Worker
    After=network.target
    
    [Service]
    Type=simple
    User=www-data
    Group=www-data
    WorkingDirectory=/var/www/invoiceninja
    ExecStart=/usr/bin/php /var/www/invoiceninja/artisan queue:work --sleep=3 --tries=3 --timeout=90
    Restart=on-failure
    RestartSec=5s
    
    [Install]
    WantedBy=multi-user.target

    Enable service:

    bash
    sudo systemctl daemon-reload
    sudo systemctl enable --now invoiceninja-queue.service

    Cron Jobs

    Add to crontab:

    bash
    sudo crontab -e -u www-data
    
    # Add this line
    * * * * * cd /var/www/invoiceninja && php artisan schedule:run >> /dev/null 2>&1

    Email Configuration

    Configure in .env:

    bash
    # SMTP Settings
    MAIL_MAILER=smtp
    MAIL_HOST=smtp.gmail.com
    MAIL_PORT=587
    MAIL_USERNAME=your-email@gmail.com
    MAIL_PASSWORD=your-app-password
    MAIL_ENCRYPTION=tls
    MAIL_FROM_ADDRESS=noreply@invoice.example.com
    MAIL_FROM_NAME="Invoice Ninja"
    
    # Alternative: Mailgun
    MAIL_MAILER=mailgun
    MAILGUN_DOMAIN=mg.example.com
    MAILGUN_SECRET=key-xxxxxxxxxxxxxxxxxxxxxxxx
    MAILGUN_ENDPOINT=api.mailgun.net
    
    # Alternative: SendGrid
    MAIL_MAILER=sendgrid
    SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxxxxxx

    Payment Gateways

    1. Stripe:

    bash
    # In .env
    STRIPE_PUBLIC_KEY=pk_live_xxxxx
    STRIPE_SECRET_KEY=sk_live_xxxxx
    STRIPE_WEBHOOK_SECRET=whsec_xxxxx

    2. PayPal:

    bash
    # In .env
    PAYPAL_CLIENT_ID=xxxxx
    PAYPAL_SECRET=xxxxx
    PAYPAL_MODE=live  # or sandbox

    3. Square:

    bash
    # In .env
    SQUARE_APPLICATION_ID=xxxxx
    SQUARE_ACCESS_TOKEN=xxxxx
    SQUARE_LOCATION_ID=xxxxx

    Advanced Configuration

    Multi-Company Setup

    bash
    # Enable multi-company
    MULTI_DB_ENABLED=true
    
    # Configure per-company databases
    DB_HOST1=db1.example.com
    DB_DATABASE1=company1
    DB_USERNAME1=user1
    DB_PASSWORD1=pass1
    
    DB_HOST2=db2.example.com
    DB_DATABASE2=company2
    DB_USERNAME2=user2
    DB_PASSWORD2=pass2

    Custom PDF Templates

    1. Create custom design:

    php
    // resources/views/pdf/custom_invoice.blade.php
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            /* Custom CSS */
        </style>
    </head>
    <body>
        <!-- Custom HTML template -->
    </body>
    </html>

    2. Register design:

    bash
    php artisan make:design CustomInvoice

    API Configuration

    bash
    # Enable API
    API_SECRET=your-api-secret
    
    # Rate limiting
    RATE_LIMIT_PER_MINUTE=60
    
    # API version
    API_VERSION=v1

    Webhook Configuration

    bash
    # Webhook URL
    WEBHOOK_URL=https://your-app.com/webhooks/invoiceninja
    
    # Webhook events
    WEBHOOK_EVENTS=invoice.created,payment.completed,client.created

    Security Configuration

    Two-Factor Authentication

    bash
    # Enable 2FA
    ENABLE_2FA=true
    
    # 2FA methods
    TWO_FACTOR_METHODS=google,sms,email

    IP Whitelisting

    bash
    # Whitelist IPs
    IP_WHITELIST=192.168.1.0/24,10.0.0.0/8
    
    # Blacklist IPs
    IP_BLACKLIST=1.2.3.4,5.6.7.8

    Security Headers

    Add to nginx configuration:

    nginx
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob: wss:;" 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;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    9. Backup and Restore

    Backup Script

    bash
    #!/bin/bash
    # backup-invoiceninja.sh
    
    BACKUP_DIR="/backup/invoiceninja/$(date +%Y%m%d_%H%M%S)"
    mkdir -p "$BACKUP_DIR"
    
    # Backup database
    mysqldump -u invoiceninja -p invoiceninja > "$BACKUP_DIR/database.sql"
    
    # Backup files
    tar czf "$BACKUP_DIR/files.tar.gz" \
      -C /var/www/invoiceninja \
      storage/app \
      public/storage \
      .env
    
    # Backup nginx config
    cp /etc/nginx/sites-available/invoiceninja "$BACKUP_DIR/"
    
    echo "Backup completed: $BACKUP_DIR"

    Restore Script

    bash
    #!/bin/bash
    # restore-invoiceninja.sh
    
    BACKUP_DIR="/backup/invoiceninja/20231201_120000"
    
    # Stop services
    sudo systemctl stop nginx php8.1-fpm invoiceninja-queue
    
    # Restore database
    mysql -u invoiceninja -p invoiceninja < "$BACKUP_DIR/database.sql"
    
    # Restore files
    tar xzf "$BACKUP_DIR/files.tar.gz" -C /var/www/invoiceninja/
    
    # Set permissions
    sudo chown -R www-data:www-data /var/www/invoiceninja
    sudo chmod -R 755 /var/www/invoiceninja
    sudo chmod -R 775 /var/www/invoiceninja/storage
    sudo chmod -R 775 /var/www/invoiceninja/bootstrap/cache
    
    # Clear cache
    cd /var/www/invoiceninja
    php artisan cache:clear
    php artisan config:clear
    php artisan view:clear
    
    # Start services
    sudo systemctl start nginx php8.1-fpm invoiceninja-queue
    
    echo "Restore completed from: $BACKUP_DIR"

    Performance Optimization

    PHP-FPM Tuning

    Edit /etc/php/8.1/fpm/pool.d/www.conf:

    ini
    pm = dynamic
    pm.max_children = 50
    pm.start_servers = 10
    pm.min_spare_servers = 5
    pm.max_spare_servers = 20
    pm.max_requests = 500
    
    ; Increase memory and execution limits
    php_admin_value[memory_limit] = 256M
    php_admin_value[max_execution_time] = 300
    php_admin_value[post_max_size] = 50M
    php_admin_value[upload_max_filesize] = 50M

    Redis Optimization

    bash
    # Configure Redis
    sudo nano /etc/redis/redis.conf
    
    # Add/modify:
    maxmemory 1gb
    maxmemory-policy allkeys-lru
    save ""  # Disable persistence for cache

    Database Optimization

    sql
    -- Optimize tables
    OPTIMIZE TABLE clients, invoices, payments, products;
    
    -- Add indexes
    CREATE INDEX idx_invoices_client_id ON invoices(client_id);
    CREATE INDEX idx_invoices_date ON invoices(invoice_date);
    CREATE INDEX idx_payments_invoice_id ON payments(invoice_id);

    Laravel Optimization

    bash
    # Cache configuration
    php artisan config:cache
    php artisan route:cache
    php artisan view:cache
    
    # Optimize autoloader
    composer install --optimize-autoloader --no-dev
    
    # Enable OPcache
    sudo phpenmod opcache

    Monitoring

    Health Checks

    bash
    # Check application health
    curl https://invoice.example.com/api/v1/health
    
    # Check queue status
    php artisan queue:monitor
    
    # Check failed jobs
    php artisan queue:failed

    Logging

    Configure in .env:

    bash
    # Log channel
    LOG_CHANNEL=daily
    
    # Log level
    LOG_LEVEL=error
    
    # Log retention
    LOG_DEPRECATIONS_CHANNEL=null
    LOG_DAYS=14

    Application Monitoring

    bash
    # Enable Laravel Telescope (development)
    composer require laravel/telescope --dev
    php artisan telescope:install
    php artisan migrate
    
    # Enable Sentry (production)
    SENTRY_LARAVEL_DSN=https://xxxxx@sentry.io/xxxxx

    6. Troubleshooting

    Common Issues

    1. 500 Internal Server Error:

    bash
    # Check permissions
    sudo chown -R www-data:www-data /var/www/invoiceninja
    sudo chmod -R 755 /var/www/invoiceninja
    sudo chmod -R 775 /var/www/invoiceninja/storage
    sudo chmod -R 775 /var/www/invoiceninja/bootstrap/cache
    
    # Clear cache
    php artisan cache:clear
    php artisan config:clear
    php artisan view:clear

    2. PDF generation issues:

    bash
    # Install required packages
    sudo apt install -y chromium-browser
    
    # Configure in .env
    PDF_GENERATOR=snappdf
    SNAPPDF_EXECUTABLE_PATH=/usr/bin/chromium-browser

    3. Email not sending:

    bash
    # Test email configuration
    php artisan tinker
    >>> Mail::raw('Test email', function($message) {
    ...     $message->to('test@example.com')->subject('Test');
    ... });
    
    # Check queue
    php artisan queue:work --once

    Debug Mode

    Enable debug mode (development only):

    bash
    # In .env
    APP_DEBUG=true
    APP_ENV=local
    
    # View logs
    tail -f storage/logs/laravel.log

    Upgrade Process

    bash
    # Backup first!
    ./backup-invoiceninja.sh
    
    # Put application in maintenance mode
    php artisan down
    
    # Pull latest code
    git fetch --tags
    git checkout tags/v5.x.x
    
    # Update dependencies
    composer install --no-dev
    npm install
    npm run production
    
    # Run migrations
    php artisan migrate --force
    
    # Clear caches
    php artisan cache:clear
    php artisan config:cache
    php artisan view:cache
    
    # Bring application back online
    php artisan up

    Additional Resources

  • Official Documentation
  • GitHub Repository
  • Community Forum
  • API Documentation
  • Slack Community
  • Video Tutorials
  • ---

    Note: This guide is part of the HowToMgr collection. Always refer to official documentation for the most up-to-date information.