Ghost installed and configured, but not starting

Hello!

I installed Ghost on Ubuntu 20.04 with OpenLiteSpeed. I followed this guide here:
https://openlitespeed.org/kb/ghost-openlitespeed/

As far as I know, everything has been configured on the server properly, and I’m now trying to get Ghost to run, but whenever I visit the URL of my Ghost instance, the request doesn’t complete.

I checked the Ghost error log and found this:

{
  "name": "Log",
  "hostname": "blog.example.org",
  "pid": 1259007,
  "level": 50,
  "err": {
    "id": "7531a510-060e-11ed-aecc-178b1de9c182",
    "domain": "https://blog.example.org",
    "code": null,
    "name": "InternalServerError",
    "statusCode": 500,
    "level": "critical",
    "message": "Knex: run\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open",
    "stack": "Error: Knex: run\n$ npm install mysql2 --save\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open\n    at Client_MySQL2.initializeDriver (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/client.js:194:13)\n    at new Client (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/client.js:75:12)\n    at new Client_MySQL (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/dialects/mysql/index.js:21:1)\n    at new Client_MySQL2 (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/dialects/mysql2/index.js:9:1)\n    at knex (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/knex-builder/Knex.js:16:28)\n    at Object.connect (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex-migrator/lib/database.js:36:12)\n    at KnexMigrator.isDatabaseOK (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex-migrator/lib/index.js:566:32)\n    at DatabaseStateManager.getState (/home/blog.example.org/public_html/versions/5.4.0/core/server/data/db/state-manager.js:40:37)\n    at DatabaseStateManager.makeReady (/home/blog.example.org/public_html/versions/5.4.0/core/server/data/db/state-manager.js:73:36)\n    at initDatabase (/home/blog.example.org/public_html/versions/5.4.0/core/boot.js:69:26)\n    at bootGhost (/home/blog.example.org/public_html/versions/5.4.0/core/boot.js:419:15)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)",
    "hideStack": false
  },
  "msg": "Knex: run\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open",
  "time": "2022-07-17T20:24:28.387Z",
  "v": 0
}

It looks like it’s failing to install mysql2, because it already exists, but I’m not sure what it means in order to troubleshoot this further on my own.

Along the lines of database, I should note that I have MariaDB installed on the server that will be running Ghost, however I’ve configured a remote database server with MySQL 8 for Ghost to use.

When I run ghost start, it does appear to boot up, because that’s when it created the database tables, however when I try to run Ghost using OpenLiteSpeed (as documented in the instructions on the link above), I get the error.

Any guidance to resolve this would be appreciated!

Only MySQL8 is supported in production as of Ghost >5.0 - official docs here:

1 Like

Hi John! I got mixed up. MariaDB is installed on the server that’s running Ghost, however I’m using a remote database with MySQL 8. I just updated my original post (because it was held in moderation, so I couldn’t do it sooner). I realize that nginx is preferred, and considering that ghost start seems to run Ghost okay (but can’t access it, because OpenLiteSpeed didn’t start it), do you have any ideas what the issue is?

I presume you’ve checked the logs with something like journalctl -u 'ghost*'.

You can confirm if the issue involves OpenLightSpeed by trying to connect directly to Ghost. If that works, the issue with the OpenLightSpeed config.

  1. SSH to the server
  2. Using a tool like httpie, try connecting directly to the Ghost blog app port: http 127.0.0.1:2369. If Ghost is working, it should respond with an HTTP request to redirect you to the SSL version of the URL:
http 127.0.0.1:2369
HTTP/1.1 301 Moved Permanently
Cache-Control: public, max-age=31536000
Connection: keep-alive
Content-Length: 57
Content-Type: text/plain; charset=utf-8
Date: Mon, 18 Jul 2022 20:06:59 GMT
Keep-Alive: timeout=5
Location: https://127.0.0.1:2369/
Vary: Accept, Accept-Encoding
X-Powered-By: Express

Moved Permanently. Redirecting to https://127.0.0.1:2369/

You could use curl or wget for testing this as well.

1 Like

Hi @markstos When I ran journalctl, it didn’t return any data.

I did make a cURL request to http://localhost:2369 and saw the Moved Permanently response:

curl -I http://127.0.0.1:2369/
HTTP/1.1 301 Moved Permanently
X-Powered-By: Express
Cache-Control: public, max-age=31536000
Location: https://127.0.0.1:2369/
Vary: Accept, Accept-Encoding
Content-Type: text/plain; charset=utf-8
Content-Length: 57
Date: Wed, 20 Jul 2022 01:42:35 GMT
Connection: keep-alive
Keep-Alive: timeout=5

When I did it with https, I got this:

curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number

Which kind of makes sense, because I didn’t set up SSL through Ghost.

If I upload a very simple Node app (hello.js) into my Ghost project:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World from Node!\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

And then update the context in OpenLiteSpeed to point to current/hello.js instead current/index.js, OpenLiteSpeed is working just fine.

Also considering that I’m not seeing any errors in OpenLiteSpeed, but rather the Ghost application, I’m thinking Ghost is loading/behaving differently when I use OpenLiteSpeed vs the Node itself, but I don’t know what it is.

Here is the full log when Ghost tries to start, not just the error:

{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Ghost is running in production...","time":"2022-07-20T02:03:36.672Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Your site is now available on https://blog.example.org/","time":"2022-07-20T02:03:36.674Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Ctrl+C to shut down","time":"2022-07-20T02:03:36.674Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Ghost is running in production...","time":"2022-07-20T02:03:36.675Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Your site is now available on https://blog.example.org/","time":"2022-07-20T02:03:36.676Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Ctrl+C to shut down","time":"2022-07-20T02:03:36.676Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":30,"msg":"Ghost server started in 0.991s","time":"2022-07-20T02:03:36.677Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":50,"err":{"id":"2aa43530-07d0-11ed-98e1-ffb1d6c98294","domain":"https://blog.example.org","code":null,"name":"InternalServerError","statusCode":500,"level":"critical","message":"Knex: run\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open","stack":"Error: Knex: run\n$ npm install mysql2 --save\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open\n    at Client_MySQL2.initializeDriver (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/client.js:194:13)\n    at new Client (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/client.js:75:12)\n    at new Client_MySQL (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/dialects/mysql/index.js:21:1)\n    at new Client_MySQL2 (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/dialects/mysql2/index.js:9:1)\n    at knex (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex/lib/knex-builder/Knex.js:16:28)\n    at Object.connect (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex-migrator/lib/database.js:36:12)\n    at KnexMigrator.isDatabaseOK (/home/blog.example.org/public_html/versions/5.4.0/node_modules/knex-migrator/lib/index.js:566:32)\n    at DatabaseStateManager.getState (/home/blog.example.org/public_html/versions/5.4.0/core/server/data/db/state-manager.js:40:37)\n    at DatabaseStateManager.makeReady (/home/blog.example.org/public_html/versions/5.4.0/core/server/data/db/state-manager.js:73:36)\n    at initDatabase (/home/blog.example.org/public_html/versions/5.4.0/core/boot.js:69:26)\n    at bootGhost (/home/blog.example.org/public_html/versions/5.4.0/core/boot.js:419:15)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)","hideStack":false},"msg":"Knex: run\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open","time":"2022-07-20T02:03:36.838Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":40,"msg":"Ghost is shutting down","time":"2022-07-20T02:03:36.839Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":40,"msg":"Ghost has shut down","time":"2022-07-20T02:03:36.857Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":40,"msg":"Your site is now offline","time":"2022-07-20T02:03:36.858Z","v":0}
{"name":"Log","hostname":"example","pid":1417303,"level":40,"msg":"Ghost was running for a few seconds","time":"2022-07-20T02:03:36.859Z","v":0}

I did notice that it seems to start twice? And the second is when the error is produced. That said, going to the website address doesn’t resolve anything.

Just for testing, I used --db=sqlite3 when I ran ghost install. Ghost started just fine, and I was able to access the front-end and back-end. This tells me OpenLiteSpeed is doing what it needs to do, but for some reason Ghost will not run, even though the MySQL 8 requirements is met, because it’s a remote database.

Try copying the connection details from your config.production.json an using those to build a mysql command line connection from the web server to the remote DB server. Can you connect to the DB fine if you do it outside of the application?

Also, what response is OpenLiteSpeed getting from Ghost? 200? 502? That should be in the OpenLightSpeed logs.

1 Like

Hi @markstos I know Ghost has the ability to connect to the database, because when I run ghost start, it creates all of the database tables in the MySQL database. When I start Ghost with OpenLiteSpeed, the database remains untouched. So I’m not sure if it’s failing before or after it attempts to connect to the MySQL database; presumably before, because no tables get created.

I see one entry in the OpenLiteSpeed access log:

"0.0.0.0 - - [21/Jul/2022:03:07:06 +0000] "GET / HTTP/2" 500 223 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36""

It looks like it’s a 500 error, which is consistent with this part in the Ghost log:

{"name":"InternalServerError"}

The error log is empty.

The error message says the files that result from npm to install mysql2 already exist.

This is some kind of inconsistent state. Find those files and remove them so the command can succeed.

1 Like

Hi @markstos That is a good thought, and I tried that as well. Specifically, I went into versions/5.4.1/node_modules and deleted the mysql2 directory. I then restarted OpenLiteSpeed and attempted to access the website, but then it complains that it can’t find the module:

{
  "name": "Log",
  "hostname": "example",
  "pid": 1581157,
  "level": 50,
  "err": {...},
  "msg": "Knex: run\n$ npm install mysql2 --save\nCannot find module 'mysql2'\nRequire stack:\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/dialects/mysql2/index.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/dialects/index.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/knex-builder/internal/config-resolver.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/knex-builder/Knex.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/index.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/knex.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex-migrator/lib/database.js\n- /home/blog.example.org/public_html/versions/5.4.1/node_modules/knex-migrator/lib/index.js\n- /home/blog.example.org/public_html/versions/5.4.1/core/server/data/db/state-manager.js\n- /home/blog.example.org/public_html/versions/5.4.1/core/boot.js\n- /home/blog.example.org/public_html/versions/5.4.1/ghost.js\n- /home/blog.example.org/public_html/versions/5.4.1/index.js\n- /usr/local/lsws/fcgi-bin/lsnode.js",
  "time": "2022-07-22T16:57:42.886Z",
  "v": 0
}

Do we know what specifically might be getting executed that tries to run npm install mysql2 that leads to this error? I don’t have any experience with Ghost and I’m not sure what Knex is or what role it plays in the setup/installation of Ghost.

It looks like you aren’t using a normal proxying setup, but are using the lsnode module of OpenLiteSpeed, a project which appears it hasn’t been updated in four years. I would not be surprised if it works for simple apps like your “hello world” test, but fails for complex apps like Ghost. In your stacktrace, there’s this at the end of the stacktrace:

/usr/local/lsws/fcgi-bin/lsnode.js

It appears to be a reference to this: GitHub - rperper/lsnode

Instead of using that, try using OpenLiteSpeed as a normal proxy and cut lsnode out of the pipeline.

1 Like

I suspect the root cause might have something to do with lsnode and environment variables. Like this:

  1. npm install mysql2 succeeds in installing mysql2
  2. The code immediately tries to use the module, using environment variables to resolve the directories to search for modules in. This fails, resulting in in Cannot find module immediately after it was installed.

For example, this could happen if lsnode has a different idea of what the “current working is” then Ghost does, the fact that they working together could create a problematic mismatch.

If you want to check this idea, modify lsnode.js and have it dump all the environment variables as soon as it starts. Do the same for Ghost index.js. Now look at the result when running Ghost directly and through lsnode.js. Are the environment variables to Ghost the same or different? How are the different?

1 Like

Hi @markstos This is all good information; I’m going to take some time to digest and play with this. Looking at the lsnode repo, ironically the sample project it discusses is a Ghost blog, but as you pointed out, it’s 4 years old, and perhaps some things have changed. Also noted on the environment variables. Thanks for sticking with me on this!

1 Like

The way I found the lsnode mention was copying your stacktrace into an editor and replacing all the “\n” mentions with actual newlines, which made the stacktrace much easier to read.

1 Like

Hi @markstos Through some hackery, I was able to get a state of the config object Ghost has right before it calls initDatabase within core.js when it’s configured for MySQL and SQLite.

Here are the differences.

config.stores.custom-env.store

MySQL

"database": {
  "client": "mysql",
  "connection": {
      "database": "database",
      "host": "0.0.0.0",
      "password": "password",
      "port": 3306,
      "user": "user"
  }
},

SQLite

"database": {
    "client": "sqlite3",
    "connection": {
        "filename": "/home/blog.example.org/public_html/content/data/ghost.db"
    }
},

Everything makes sense here.

config.stores.default-env.store

MySQL

"database": {
  "client": "mysql",
  "connection": {
      "database": "database",
      "host": "0.0.0.0",
      "password": "password",
      "port": 3306,
      "user": "user"
  }
},

SQLite

"database": {
    "client": "mysql",
    "connection": {
        "filename": "/home/blog.example.org/public_html/content/data/ghost.db"
    }
},

It’s weird that the default client is mysql with a path to ghost.db, but since this is the SQLite configuration, which works, I guess it’s a non-issue.

config.stores.overrides

MySQL

"mtimes": {
    "database": 1658521322225,
    "database:client": 1658521322224,
    "env": 1658521322225,
    "paths:adminAssets": 1658521322224,
    "paths:adminAuthAssets": 1658521322224,
    "paths:adminViews": 1658521322224,
    "paths:appRoot": 1658521322224,
    "paths:assetSrc": 1658521322224,
    "paths:corePath": 1658521322224,
    "paths:defaultRouteSettings": 1658521322224,
    "paths:defaultSettings": 1658521322224,
    "paths:defaultViews": 1658521322224,
    "paths:fixtures": 1658521322224,
    "paths:helperTemplates": 1658521322224,
    "paths:internalAdaptersPath": 1658521322224,
    "paths:internalAppPath": 1658521322224,
    "paths:migrationPath": 1658521322224,
    "paths:publicFilePath": 1658521322224
},

SQLite

"mtimes": {
    "database": 1658521748449,
    "env": 1658521748449,
    "paths:adminAssets": 1658521748448,
    "paths:adminAuthAssets": 1658521748448,
    "paths:adminViews": 1658521748448,
    "paths:appRoot": 1658521748448,
    "paths:assetSrc": 1658521748448,
    "paths:corePath": 1658521748448,
    "paths:defaultRouteSettings": 1658521748449,
    "paths:defaultSettings": 1658521748448,
    "paths:defaultViews": 1658521748448,
    "paths:fixtures": 1658521748448,
    "paths:helperTemplates": 1658521748448,
    "paths:internalAdaptersPath": 1658521748449,
    "paths:internalAppPath": 1658521748449,
    "paths:migrationPath": 1658521748449,
    "paths:publicFilePath": 1658521748449
},

MySQL has a database:client entry, whereas SQLite does not. That said, it makes sense why there would be an entry for it.

config.stores.overrides.store.database

MySQL

"database": {
  "client": "mysql2",
  "connection": {
      "database": "database",
      "host": "0.0.0.0",
      "password": "password",
      "port": 3306,
      "user": "user"
  }
},

SQLite

"database": {
    "client": "sqlite3",
    "connection": {
        "filename": "/home/blog.example.org/public_html/content/data/ghost.db"
    }
},

I find it noteworthy that here in the MySQL config the client is now set to mysql2 instead of mysql. I would think that all database configurations would be the same?

Minus a few timestamps, everything else was identical, including every single path in each of the env properties.

Do you really have 0.0.0.0 in your config file or is a placeholder when you post here?

That means “any network interface”. So it’s valid for MySQL to listen on 0.0.0.0, but it doesn’t make sense as a server address to connect to.

Also, Putting the SQLite db filename in the MySQL connection object doesn’t make sense either.

1 Like

Hi @markstos Great question, and I should have clarified that the server name, hostname, and database information was replaced with a placeholder.

I wonder if the issue is related to the mysql and mysql2 drivers. I can imagine a scenario where the mysql driver is loaded, and then when Ghost attempts to load the mysql2 driver, it looks like it’s already running.

I can try to replace mysql with mysql2 in my connection info to test.

If I change the driver from mysql to mysql2 in config.production.json, the config object changes as follows:

  • config.stores.custom-env.store
    • mysql2 (changed)
  • config.stores.default-env.store
    • mysql (unchanged)
  • config.stores.overrides
    • mysql2 (unchanged)

But the same error persists:

{
  "name": "Log",
  "hostname": "example",
  "pid": 1595966,
  "level": 50,
  "err": {
    "id": "539eb700-0a08-11ed-8142-a3d309f212d8",
    "domain": "https://blog.example.org",
    "code": null,
    "name": "InternalServerError",
    "statusCode": 500,
    "level": "critical",
    "message": "Knex: run\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open",
    "stack": "Error: Knex: run\n$ npm install mysql2 --save\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open\n    at Client_MySQL2.initializeDriver (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/client.js:194:13)\n    at new Client (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/client.js:75:12)\n    at new Client_MySQL (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/dialects/mysql/index.js:21:1)\n    at new Client_MySQL2 (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/dialects/mysql2/index.js:9:1)\n    at knex (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex/lib/knex-builder/Knex.js:16:28)\n    at Object.connect (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex-migrator/lib/database.js:36:12)\n    at KnexMigrator.isDatabaseOK (/home/blog.example.org/public_html/versions/5.4.1/node_modules/knex-migrator/lib/index.js:566:32)\n    at DatabaseStateManager.getState (/home/blog.example.org/public_html/versions/5.4.1/core/server/data/db/state-manager.js:40:37)\n    at DatabaseStateManager.makeReady (/home/blog.example.org/public_html/versions/5.4.1/core/server/data/db/state-manager.js:73:36)\n    at initDatabase (/home/blog.example.org/public_html/versions/5.4.1/core/boot.js:69:26)\n    at bootGhost (/home/blog.example.org/public_html/versions/5.4.1/core/boot.js:420:15)\n    at processTicksAndRejections (node:internal/process/task_queues:96:5)",
    "hideStack": false
  },
  "msg": "Knex: run\n$ npm install mysql2 --save\nEEXIST: file already exists, uv_pipe_open",
  "time": "2022-07-22T21:50:39.730Z",
  "v": 0
}

It is interesting that the stack still has a reference to Client_MySQL and Client_MySQL2, and also that there are two lines of $ npm install mysql2 --save.

Does that mean the first line ran, and then it actually does try to run it again, which is what causes the EEXIST: file already exists, uv_pipe_open?

Did you try taking lsnode out of the equation?

1 Like