PDF attachments are broken

Hi there,

I was running alpine-4.5.26 when a client notified me today that the attached PDF was broken. It is 1kb in size.

I checked the forum, found a couple of similar but older threads and updated to alpine-4 so now I am on 4.5.30 and the problem persists.

I tried both using a local phantomjs bin, precompiled download, which has worked for the last 3-4 years and switching to phantomjs cloud gives the same results: “Error
Failed to load PDF document.”

I have another 30 days before the next recurring invoices get sent out and I’d like to sort this issue out. Any help or hints please?


I suggest reviewing the docs here:


Thanks for the link, I’ll go through it and see if it helps.


  • Check a company name is set on Settings > Company Details

  • Check storage/logs/laravel-error.log for relevant errors.
    The only file in that folder is .gitignore

  • To determine the path you can run which phantomjs from the command line.
    which phantomjs => /usr/local/bin/phantomjs

  • We suggest using PhantomJS version >= 2.1.1, users have reported seeing ‘Error: 0’ with older versions.
    phantomjs -v => 2.1.1

  • You can use this script to test from the command line, change __YOUR_LINK_HERE__ to the link in the error and then run phantomjs test.pjs .
    I am confused: what link in what error?

  • It may help to test with HTTP to check the problem isn’t related to the SSL certificate.

  • You may need to add an entry in the /etc/hosts file to resolve the domain name.

  • If you require contacts to enter a password to see their invoice you’ll need to set a random value for PHANTOMJS_SECRET .
    I’ve had this in for the last 4 years.

  • If you’re using a proxy and/or self-signed certificate this comment may help.
    I have been running my instance behind traefik for 4 years, no problem with IN until now. Certificate is from letsencrypt and valid

  • If you’re using a custom design try using a standard one, if the PDF is outside the printable area it can fail.
    I slightly modified an IN design and it has been working for the last 4 years. CHanging back to a default clean design did not help

  • If you’re using a non-English language try changing to English.
    I am using English.

  • It may help to use the pre-compiled binary from phantomjs.org.
    That is where I got my version 2.1.1 from

  • If all else fails you can try changing the code to prevent SSL checks.
    I don’t see the point, I don’t “think” I have SSL issues and the link provided here is broken anyway.

Did anything change?

If the PDF fails there would typically be a log in the error log, maybe your server is failing to write the errors to storage/logs?

You might be onto something. I ran IN for a couple of years based on this image: image: invoiceninja/invoiceninja without a tag (I think I was on 4.5.17

Then it all broke with an update and I was advised to move to alpine-4 please also see this thread: https://github.com/invoiceninja/dockerfiles/issues/182

Which seemed to work but ended in a permission problem indeed and I was told to sudo chown -R 1500:1550 docker/app/ as permissions / owner ship seems to have been changed.

This fixed all apparent problems but this is the first recurring invoice going out since then so I only noticed the PDF problem today.

This is what my folder containing IN looks like:

/home/ovi/docker/invoiceninja# ls -al
total 22916
drwxr-xr--  6 root root       4096 Feb  1 10:47 .
drwxr-xr-x 27 root root       4096 Apr 10  2020 ..
-rwxr-xr--  1 root root       1500 Feb  1 09:47 docker-compose.yml
-rwxr-xr--  1 root root       1573 Feb  1 09:51 .env
drwxr-xr-x  2 1500     82     4096 Jan 18 08:20 logo
drwxr-xr--  6  999 docker     4096 Feb  1 12:40 mysql
drwxr-xr--  2 root root       4096 Jul 12  2018 nginx
drwxr-xr--  9 1500     82     4096 Aug 10 10:10 storage

Even if I chown -R 1500:1550 logo/ storage/ after starting the stack it reverts to what you see: 1500:82

Any hints or links as to what container runs as which user and needs access to which mounted folder?

the IN app container mounts these volumes:

  • /home/ovi/docker/invoiceninja/storage:/var/www/app/storage:rw
  • /home/ovi/docker/invoiceninja/logo:/var/www/app/public/logo:rw

the web container:

  • /home/ovi/docker/invoiceninja/nginx/nginx.conf:/etc/nginx/nginx.conf:ro

If needed I can share my docker-compose.yml file too.

I’d be curious to see what your docker-compose.yml and .env file (if you use it) look like. (Of course, redact personal details).

I don’t mind sharing. Keep in mind I haven’t touched this since I started using IN a couple of years ago.

I’m also considering finally taking the time to make the plunge to IN v5 - as far as as I know I have to setup a new In instance v5, which means I need to use a new URL, different from the old one then do the migration from v4 to v5

Might be easier than trying to fix this old v4 install - any thoughts?

version: "2"

    image: mysql:5
    container_name: invoiceninja_db
    env_file: .env
    restart: unless-stopped
      - /home/ovi/docker/invoiceninja/mysql:/var/lib/mysql
    network_mode: bridge

    image: invoiceninja/invoiceninja:alpine-4
    container_name: invoiceninja_app
      - db:mysql
    env_file: .env
    restart: unless-stopped
      - /home/ovi/docker/invoiceninja/storage:/var/www/app/storage:rw
      - /home/ovi/docker/invoiceninja/logo:/var/www/app/public/logo:rw
    network_mode: bridge

    image: nginx:1
    container_name: invoiceninja_web
    restart: unless-stopped
      - "traefik.backend=invoicing"
      - "traefik.frontend.rule=Host:my.domain.tld"
      - "traefik.port=80"
      - "traefik.enable=true"
      - /home/ovi/docker/invoiceninja/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - app
      - app
      - 80
    network_mode: bridge

    image: invoiceninja/invoiceninja:alpine-4
    container_name: invoiceninja_cron
    restart: unless-stopped
      - db:mysql
    env_file: .env
      - app
    entrypoint: |
      sh -c 'sh -s <<EOF
      trap "break;exit" SIGHUP SIGINT SIGTERM
      sleep 300s
      while /bin/true; do
        ./artisan ninja:send-invoices
        ./artisan ninja:send-reminders
        sleep 1h
    network_mode: bridge
# all .env variables are listed here:
# https://github.com/invoiceninja/invoiceninja/blob/master/.env.example
# DB config
# APP config
# DB details for APP
# Email config
MAIL_FROM_NAME="company name"
# Proxy configuration
#Google Maps integration
# Create a cookie to stay logged in
# Immediately expire cookie on the browser closing
# The app automatically logs the user out after this number of seconds
# password for mobile app usage
# adding my phantomjs API key
# details: https://github.com/invoiceninja/invoiceninja/blob/master/docs/configure.rst#phantomjs
# alternatively use a local instance
# https://www.invoiceninja.com/forums/topic/self-hosted-pdf-in-invoice-email-bad/#post-15319

Indeed, seems like your using PHANTOMJS - that’s a real pain I’ve found. In my v4 install, I did have the phamtomjs binary local:
Not sure that would make a difference… I wonder also if you get any info from your laravel.log. Hopefully, others can give you a better answer than I can.

But, as far as going to v5 - you’re on docker, so this can be done, and v4 and v5 can be run at the same time. I just put my v4 InvoiceNinja on a (different than v5) exposed local port and refer to that going forward while I migrate the two and eventually shut down v4. In my experience, the internal migration process didn’t work - but you may have more success.

The problem persists with local phantomjs as well as with the cloud version. I am running behind traefik as reverse proxy and the proxy decides where to route based on domain. I might try and differentiate between 4 and 5 by some other means.

btw. I wonder why there is a migration routine when there already is export / import available. Would the result not be the same?

@ovizii - while I’m just a user, I can’t explain the motivation behind the migration.

But, I can suppose a few things. I think first and foremost the import/export process doesn’t handle internal configuration stuff like company name, localization, etc. Just the big info like invoices or expenses.

Secondly, v4 export to v5 import didn’t work great in my experience. They may have improved that greatly in the meantime, but I had to enter 100% of my v4 data into the v5 install manually. All of it.

When you get into it, you’ll see. Be ready for some challenges, though I hope it all goes smoothly.