Start with two accounts in your own name, a GitHub repository and a hosting account, before you export anything. How to self host an exported app comes down to that: the code goes into your repository, a written pipeline deploys it to your hosting, and one test proves it, a fresh clone deployed to staging with no builder step.

What an independent repository and hosting means

An independent repository and hosting means 5 things are true: the code is in a repository you own, the app runs on a hosting account you control, you hold the production secrets, the deploy steps are written down, and someone new can clone the repository and deploy a working staging copy.

This control is deliverable 7.11 in the Production Hardening Sprint’s published scope. Of everything in DevOps for startups: the release path, it is the piece the rest stands on: a deploy gate, a rollback and a staging copy each need a repository and a host you control before they can exist.

Each of the five lines can be checked on its own:

  • The code sits in a version-controlled repository whose owner is your account or your organization.
  • The app runs on a hosting account you control and pay for.
  • You hold the production secrets, not only the builder.
  • The way to deploy is written down and runs from the repository.
  • A person who has never seen the builder can clone the repository, deploy to staging and get a working app.

“Self-host” on this page means hosting you control: a managed host in your name first, and your own server only when there is a reason for one. It is not the hobby meaning of running Nextcloud at home. If the word deploy is itself new, what is deploying explains it.

The builder can stay in use as an editor while its sync runs both ways. Lovable’s Git sync is two-way on one active branch at a time, and export runs only from Lovable to GitHub, so an existing external repository cannot start a Lovable project. Bolt commits each working change and “checks GitHub every 30 seconds for any updates made outside Bolt”. Either way the repository in your organization becomes the copy that counts, and the builder becomes one of the tools that writes to it.

What goes wrong without it

Five situations show the gap. The first column is what happens; the last two columns are my reading of what you are left unable to do and which of the five lines above would have covered it.

SituationWhat you cannot doWhich line fixes it
The builder changes its price or pauses the backend; on Lovable, Cloud database, storage and authentication “pause shortly after you run out of credits”Serve users or copy the data out until the account is paid againLine 2 (hosting in your name) and line 3 (your secrets)
A release breaks production and nobody can say what changedPut the last good version of the whole system backLine 4 (a written pipeline)
A second engineer or an auditor asks for the repositoryHand over something they can read, review and runLine 1 (the repository in your name)
The freelancer who set it up stops answeringDeploy, rotate a key or pay the host without themAll five, lines 2 and 3 first
An acquirer’s diligence asks who owns the hostingShow an account, an owner and a billing contact in the company’s nameLine 2 (hosting in your name)

The pause is not the end of the data. Lovable’s credits page says the data “stays safe while the services are paused” and adds: “You cannot access or export it until the services run again.” That is the moment an export you made last month is worth having.

The release row is the one my audit numbers speak to. At least 17 of the 21 third-party apps I audited had no deploy gate: every push ships straight to production with nothing checking it first. Those 21 were the third-party apps I audited in June and July 2026, 11 public vibe-coded apps and 10 held-out apps audited blind, a selected set of audited apps, not a random sample or a rate for all AI-built apps. A gate needs a repository and a pipeline to live in, so an app that deploys from a button inside the builder has nowhere to put one. Reverting inside the builder is not a rollback of the system either: Lovable’s history docs say reverting “restores your project’s code only” and “does not restore or roll back your database data”.

The app only exists inside the builder tool

An app that only exists inside the builder tool has no copy anywhere you control. It cannot be reviewed, rolled back, or handed to another engineer. The first fix is a repository in your own account, synced or exported.

In practice it looks like this: there is no repository outside the tool, production deploys happen when someone presses a button in the tool, and the database, file storage and secrets live in the tool’s built-in backend. Whether your builder exports at all, and what its export leaves out, is covered builder by builder in which AI builders let you export your code.

The developer disappeared with my source code

A developer who disappeared with the source code leaves two separate jobs. The first is finding out which accounts are already in your name and what you can recover without them. The second is making sure it never happens again: the code in a repository you own and a deploy that runs without that person.

The first job has its own guide: if your app developer will not answer at all walks through the accounts you can reach, the support routes, and where a contract or payment dispute becomes a lawyer’s question. Which assets have to be yours is listed in the nine things that have to be in your name. This page gives no legal view on who owns the code.

What this page adds is the second job. Once you are back in, rotate every secret the developer held (key rotation after a handover has its own guide in this series), and put the code into a repository you own, which is the next section. The full list for taking over an app is owning an app someone else built.

How to self host an exported app: repository, rebuild, hosting, pipeline

An exported app is self-hosted in 4 steps: move the code into a repository in your name, rebuild what the export left behind in a new environment, deploy it to a hosting account in your name, and write the pipeline down so it runs from the repository without the builder.

Each step below ends with what is now in your name.

Step 1: put the code in a repository you own

  1. 01 Create a GitHub organization owned by your own account, turn on two-factor sign-in, and make a trusted person with their own GitHub account a second owner
  2. 02 Connect the builder’s GitHub sync to that organization, or push the downloaded export to a new private repository inside it
  3. 03 Open the default branch and confirm it holds the whole app and a lockfile
  4. 04 Add the freelancer or agency to your organization as a member or outside collaborator, never the other way round
  5. 05 Tag the first commit so there is a known starting point

GitHub’s documentation on GitHub’s organization roles says the owner role “should be limited, but to no less than two people”, and it describes an outside collaborator as someone with access to organization repositories who is not a member, “such as a consultant or temporary employee”. The second owner has to be a different person, not a second account of yours: GitHub’s Terms of Service say “One person or legal entity may maintain no more than one free Account”. Whether you hold the repository in the administrator sense is the test in the do-i-own-my-app guide linked above.

The click paths differ per builder and live in their own articles: getting your app, database, files and secrets out of Replit, self-hosting a Lovable app, and moving a Base44 app to your own stack. A Base44 move rebuilds the backend rather than copying it, which that article walks through.

Now in your name: the repository, with at least two owners.

Step 2: rebuild the app and database in a new environment

Rebuilding an app and database in a new environment is only half done when it works once. The other half is writing each piece into the repository as you go: the migrations, a restore note, the list of secret names, the storage buckets, the scheduled jobs and every third-party callback URL, so the next rebuild needs nobody’s memory.

Rebuild here means standing the app up again from its parts, not the games-console meaning of rebuilding a database. What an export leaves behind for each builder, and the order to cut over in, is in what travels with the code and what stays in the builder’s account; the per-builder export answer is in the export guide linked earlier. This page prints neither.

What the repository should hold for each piece is my working rule:

PieceWhat the repository holds so the rebuild repeats
CodeThe default branch and a lockfile
Database structureA migrations folder or a schema dump, applied by the pipeline
DataA restore note: where the latest backup lives and the command that loads it, never the data itself
Stored filesThe bucket names and how their contents are copied
User accountsThe decision on password resets, written down
SecretsA list of the names and where each value is held, never the values
Server functions and scheduled jobsTheir code, plus a list of the schedules
Third-party registrationsEvery webhook URL and OAuth redirect URL, and the account each one lives in

One of my own apps that I audited shows why the table matters. In it, nothing in the repository could take or restore a database backup, and nothing in the deploy applied migrations. The one-command reset script next to them force-reset whatever database the root .env pointed at, with no confirmation. The lesson I take from it: a database the repository cannot restore or migrate still lives in somebody’s memory, so the rebuild is not yet yours.

Stand the rebuild up behind a staging domain first. The production cutover, with DNS, TLS and the old host kept warm until the new one answers, belongs to add HTTPS to a website and keep DNS clean.

Now in your name: a rebuild that the repository describes, piece by piece.

Step 3: deploy your own SaaS on a managed host first

To deploy your own SaaS, start with a managed host on an account in your own name, and run your own server only for a stated reason, such as a customer requirement or a process that must always run. The next two sections are the work you take on when you leave a managed host.

My working rule for a small app: the front end, the API or workers, and a managed Postgres go to a managed host whose account is in your name, and you are both the account owner and the billing contact. Check both on the host’s team or members page, not on an invoice someone forwarded. Which host fits which app, including atomic deploys across several services, is worked through as a requirements-first decision model in the deployment target comparison, with the dates its host documentation was checked; I don’t repeat it here.

Three reasons send people to their own server, and each one is a reason to read the next two sections rather than advice to do it. A customer may require it; what that request can mean is in a customer who wants the app on their own servers. A process may need to run all the time rather than per request. Or the load may be steady enough that a server costs less than metered hosting.

Now in your name: the hosting account and its bill.

Step 4: a documented pipeline that runs from the repository

The deploy document is one page with six lines:

  1. 01 Trigger: what starts a deploy, usually a merge to main
  2. 02 First steps: install from the lockfile, build and run the tests, and say whether the host waits for those checks or CI starts the deploy
  3. 03 Secrets: every value comes from the host’s or the CI system’s secret store, never from the repository
  4. 04 Database changes: the migration step that runs with each deploy
  5. 05 Rollback: the exact steps that put the previous version back
  6. 06 Access: the people who can start a production deploy

The second line carries a check that is easy to miss, and it is my reading of how these setups behave: on a host that builds and deploys every push by itself, tests in a separate CI job stop nothing unless the host waits for those checks or the deploy is triggered from CI, so the document says which of the two it is. The gates themselves are covered in this series’ release readiness checklist and CI/CD best practices guides, pipeline secrets in the CI/CD security guide, and migrations in the guide to running database migrations on deploy.

Staging comes before production and gets its own database. If the idea is new, start with what is a staging environment. The document lives in the repository’s README or in docs/deploy.md, so every clone carries it.

Now in your name: the way to deploy, written where the code is.

Running your own box: a process manager, a reverse proxy, and the accounts that own them

Running your own box means owning 6 pieces: the cloud account, the SSH keys, a process manager, a reverse proxy with TLS, a firewall, and backups. Each one must belong to an account or key in your name, or the server is only borrowed.

This section and the next apply only once you have decided to run a server. The decision to run one is part of the ownership control, because every piece below is another place where an account or a key can end up in someone else’s name. Every command and default below is taken from the tool’s own documentation, not from a server I ran for this article.

PieceWhat it doesCommon choicesThe account or key that owns it
Cloud accountHolds the server, its network rules and the billAmazon EC2 on AWS, or any VPS hostYour account, with you as root user and billing contact
SSH keysLet a named person log in to the serverOpenSSH keys; on EC2, the key pair chosen at launchOne key per person, held by that person
Process managerKeeps the app running, restarts it after a crash, starts it at bootPM2 for Node.js, systemd for any languageA non-root user on the server that exists only to run the app
Reverse proxy and TLSAnswers on ports 80 and 443, obtains certificates, forwards requests to the app’s local portCaddy, nginx, Apache HTTP ServerThe domain’s DNS account, in your name
FirewallBlocks inbound traffic except the ports you allowufw on the server, a security group at the cloud providerThe cloud account for the security group; your sudo users for ufw
BackupsKeep copies of the database and the server’s state off the boxDatabase dumps and provider snapshots, stored elsewhereA storage account in your name, separate from the server

The last column is my working rule. Monitoring belongs on the list too; logging and monitoring have their own area in this series.

What is PM2 in Node.js, and when systemd is enough

PM2 is a process manager for Node.js. It keeps the app running, restarts it after a crash, can run several copies behind a built-in load balancer in cluster mode, keeps logs, and starts the app at boot once its startup script is installed. On Linux, a systemd unit covers the same ground for a single process.

PM2’s home page calls it a “daemon process manager that will help you manage and keep your application online 24/7”. The two commands people miss are in PM2’s quick start: pm2 startup generates a startup script so PM2 comes back when the server reboots, and pm2 save freezes the current process list “for automatic respawn”. Skip either one and a reboot can leave the app down.

A systemd service does the same job for any language. One detail in the systemd service manual matters here: Restart= defaults to no, so a unit without it will not restart a crashed app. The [Install] section with WantedBy=multi-user.target is what systemctl enable uses to start the unit at boot, per the systemd.unit manual. User= sets the account the app runs as; without it, a system service runs as root.

[Unit]
Description=My app

[Service]
User=app
WorkingDirectory=/srv/app
EnvironmentFile=/etc/app/app.env
ExecStart=/usr/bin/node server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target

Save it as /etc/systemd/system/app.service, the directory the systemd.unit manual gives for units an administrator creates, and replace the user name and the paths with your own (which node prints where Node.js lives on your server). Then run sudo systemctl enable app.service and sudo systemctl start app.service. For a single-process app on Linux, my reading is that this unit is enough and PM2 adds little. Either way, the app runs as a non-root user and listens on a local port that only the proxy can reach.

Caddy how-to: a reverse proxy with automatic HTTPS

Caddy is a web server that works as a reverse proxy with automatic HTTPS. A short Caddyfile names your domain and forwards requests to the app’s local port. Caddy then obtains and renews the certificate itself once the conditions its docs list hold, among them DNS pointing at the server and ports 80 and 443 open.

Caddy here is the web server, not the golf job. The Caddy setup for one app has three parts. Install it from Caddy’s own packages for Debian and Ubuntu, which start Caddy “as a systemd service named caddy”. Write the Caddyfile at /etc/caddy/Caddyfile with your domain and one reverse_proxy line pointing at the app’s port, in the form Caddy’s reverse-proxy examples use; Caddy’s getting-started guide walks through the Caddyfile basics. Then check it with caddy validate --config /etc/caddy/Caddyfile and apply it with sudo systemctl reload caddy.

app.example.com {
	reverse_proxy localhost:3000
}

Caddy’s automatic HTTPS conditions are five: the domain’s A/AAAA records point to your server, ports 80 and 443 are open externally, Caddy can bind to those ports, its data directory is writeable and persistent, and the domain appears in the config. When they hold, Caddy also redirects HTTP to HTTPS without a line of config.

nginx does the same proxy job. For certificates, its current docs describe the ngx_http_acme_module, which “implements the automatic certificate management ( ACMEv2 ) protocol” and is available as a prebuilt nginx-module-acme package. On Caddy vs nginx, the difference that matters to a founder is who handles certificates and how long the config is: Caddy handles certificates by default once the site address is in its config, while nginx, to do the same by itself, needs that module installed and an issuer block configured. Certificates beyond that, HSTS and CAA records sit with the HTTPS guide linked in Step 2.

The server name in Apache and nginx

Apache’s ServerName directive “sets the request scheme, hostname and port that the server uses to identify itself”. With name-based virtual hosts, it is how Apache matches the hostname the client reports in its request headers, and failing to set it to a name the server can resolve to an IP address results in a startup warning. nginx’s equivalent is server_name, covered in nginx’s server names documentation. The ownership point is the default host: when no name matches, Apache uses the first listed virtual host and nginx uses the first server block or the one marked default_server. My rule is that the default refuses the request rather than serving your app to any hostname that points at the server.

SSH in EC2 and any other VPS: whose key is it

SSH in EC2 uses the private key of the key pair chosen at launch, a default user name that depends on the image, the instance’s public address, and a security group rule that allows SSH from your address. Give each person a key of their own, remove it the day they leave, and never share the launch key.

Start in the browser if you have not used a terminal before. AWS names two browser-based options, EC2 Instance Connect and Session Manager, which “can be used from any computer”. EC2 Instance Connect uses IAM policies to control SSH access, “removing the need to share and manage SSH keys”, and the public key it pushes “remains for 60 seconds”; the instance needs EC2 Instance Connect installed (some images come with it) and a security group that allows inbound SSH from the EC2 Instance Connect service. Session Manager works “without the need to open inbound ports, maintain bastion hosts, or manage SSH keys”; among its prerequisites are SSM Agent version 2.3.68.0 or later on the instance and outbound HTTPS on port 443 to its endpoints.

How to SSH into an EC2 instance from a terminal is set out in AWS’s guide to connecting to a Linux instance: find the .pem file for the key pair you specified at launch and run chmod 400 on it, look up the default user name for the image (ubuntu on Ubuntu, ec2-user on Amazon Linux, admin on Debian), get the public DNS name, and confirm the security group allows SSH from your IP address. Then connect with ssh -i /path/key-pair-name.pem instance-user-name@instance-public-dns-name.

My working rule on ownership holds on EC2 and on any other VPS host: the AWS root user and the billing belong to you, each person gets their own user and their own key, a departing contractor’s key comes out of authorized_keys the day they leave, and nobody uses the launch key after the first login.

Hardening the server you now run

This part applies only once your hosting is self-run. On a managed host, much of it is the host’s side of the arrangement; on your own box, all of it is yours. App-level controls such as authentication, secrets and input handling have their own areas in this series and are not repeated here. Everything below is a setting or a check on a server you own.

Server risks you take on when you leave a managed host

Server risk on a box you run comes down to 6 things the managed host used to carry: unpatched packages, password SSH, services open on public ports, no restart after a crash or reboot, a disk filled by logs, and no backup of the server’s own state.

Risk here means the machine’s security and uptime, not the RISK video game. The middle column below is my reading of what a typical managed host takes off your hands; check your own host’s docs for the exact split.

RiskWho carried it beforeWhat you now do
Unpatched operating system packagesThe host, for the platform under your appAutomatic security updates (step 6 of the first-hour list)
SSH open to password loginsThe host, which controlled any shell accessKeys only (step 3)
Services on public ports, such as a database or admin tool open to the internetThe host’s private networkBind them to localhost (step 7) and firewall the rest (steps 4 and 5)
No restart after a crash or rebootThe platform’s supervisorA process manager with restart and start at boot (the PM2 and systemd section)
A disk filled by logsThe platform’s log retentionLog rotation and a disk alert (step 9)
No backup of the box’s own stateThe platform, which rebuilt servers from your codeOff-box backups and a rebuild note (step 10)

Patching is where my audits point. In them, 9 of the 26 audited apps ran a framework version with a publicly known, reachable RCE or auth bypass, and the fix was usually a one-line version bump. Those are the 26 apps I audited in June and July 2026, 21 third-party apps and 5 of my own: a selected set, not a random sample, so read the 9 as what that group showed and not as a rate for AI-built apps in general. Those were framework versions inside the apps. On your own box, the operating system’s packages are yours to patch as well.

Server hardening in Linux: the first-hour list

Server hardening in Linux on a new VPS is a 10-step first hour: patch, add a non-root user, allow SSH keys only, deny inbound at the host and cloud firewalls, turn on automatic security updates, keep the database off public ports, run the app as its own user, rotate logs, alert on disk, and back up off the box.

  1. 01 Update every package from the distribution’s repositories
  2. 02 Create a non-root user with sudo, add your SSH public key to its ~/.ssh/authorized_keys file, and use it from now on
  3. 03 Allow SSH keys only: PasswordAuthentication no and PermitRootLogin no, checked with sudo sshd -t before the restart
  4. 04 Turn on a host firewall that denies inbound traffic by default and allows 22, 80 and 443
  5. 05 Set the cloud provider’s firewall or security group the same way
  6. 06 Turn on automatic security updates
  7. 07 Bind the database and any admin tool to localhost or the private network only
  8. 08 Run the app as its own non-root user
  9. 09 Set up log rotation and an alert on free disk space
  10. 10 Back up off the box and keep a written note of how to rebuild the server

After step 1, remove packages and services the box does not need; I do this because every running service is one more thing to patch.

Step 3 has a trap on Ubuntu. The OpenSSH sshd_config manual says that “for each keyword, the first obtained value will be used”, and that included files are “processed in lexical order”. Ubuntu’s default configuration includes /etc/ssh/sshd_config.d/*.conf at the very top of the main file, so a setting in one of those files overrides the main file. Put the two lines in a file there whose name sorts first, run sudo sshd -t, then sudo systemctl restart ssh.service. Keep your current session open and test a new login in a second terminal before you close the first; I insist on this because a wrong setting can lock you out.

# /etc/ssh/sshd_config.d/00-hardening.conf
PasswordAuthentication no
PermitRootLogin no

Both lines matter: OpenSSH’s defaults are PasswordAuthentication yes and PermitRootLogin prohibit-password. For step 4, Ubuntu’s firewall guide notes that ufw is “initially disabled”; with the ufw manual’s commands, set sudo ufw default deny incoming, then sudo ufw allow 22, sudo ufw allow 80 and sudo ufw allow 443, and only then sudo ufw enable, so the SSH rule exists before the firewall starts. The policy and that order are my own choice. For step 6, Ubuntu’s automatic updates guide says security updates are applied by the unattended-upgrades package, “which is installed by default”, so on Ubuntu the job is to confirm it is still on.

Once SSH accepts keys only, a repeated-login blocker is optional. fail2ban “scans log files like /var/log/auth.log and bans IP addresses conducting too many failed login attempts”. SELinux or AppArmor: my reading is to leave the distribution’s default on and not switch it off to make something work.

Hardening the web server

Hardening a web server means the first-hour list for the box plus 5 settings on the proxy: HTTPS only, no version banner, limits on request size and time, a default host that refuses unknown hostnames, and no public admin or status endpoint. Security headers for the application itself are set by the app.

HTTPS only comes for free on Caddy, which redirects HTTP to HTTPS automatically. On nginx, server_tokens defaults to on, which emits the nginx version “on error pages and in the “Server” response header field”; set it to off. The size and time limits are client_max_body_size (default 1m, above which nginx returns 413) and client_body_timeout (default 60s between two reads of the body, then 408); set them to what your app’s uploads actually need. For unknown hostnames, nginx’s docs show a server block that returns 444, a non-standard code that “closes the connection”. Caddy’s admin endpoint listens on localhost:2019 by default; keep it there. Security headers such as CSP belong to the app’s own security area, not the proxy.

Standard hardening configuration templates: CIS Benchmarks and what to take from them

Standard hardening configuration templates usually means the CIS Benchmarks, the vendor-neutral configuration guides the Center for Internet Security publishes per system. For one server, my working rule is to read the benchmark for your distribution as a reading list, take the commands from the distribution’s own security documentation, and never call the server CIS compliant without a scored assessment.

CIS describes the CIS Benchmarks as “100+ vendor-neutral configuration guides”, with separate benchmarks for releases such as Ubuntu Linux 24.04 LTS and Debian Linux 13. For conformance it names a separate tool, CIS-CAT Pro Assessor, to “Assess system conformance to CIS Benchmarks”, and it offers CIS Hardened Images, “Virtual images hardened to CIS Benchmarks on cloud service provider marketplaces”. What I take from them for one server: the benchmark for your distribution tells you what to check, and Ubuntu’s security documentation tells you how on Ubuntu. On NIST’s hardening guidance for servers, the document to read is NIST’s Guide to General Server Security, SP 800-123, final, published July 2008.

How to verify it: deploy a fresh clone to staging

Independence is verified by one test: someone who did not build the app clones the repository, follows only the written deploy steps, and gets a working staging copy through the pipeline. Every step that needed the builder, a person’s memory or an undocumented value is a failure to fix and run again.

The steps are my own test, written so that it can fail:

  1. 01 Someone who did not build the app, or you on a clean machine or a fresh cloud shell, clones the repository
  2. 02 They follow only the deploy document
  3. 03 They create a new staging environment from it: the database from migrations, secrets from the list of names, values from your password manager
  4. 04 They deploy through the pipeline, not from their own laptop
  5. 05 The staging app starts, a test user can sign up, sign in and do the main action, and a test payment goes through in the payment provider’s test mode
  6. 06 They write down every step that needed the builder, someone’s memory or an undocumented value, and each one counts as a failure
  7. 07 Fix each failure and run the test again until the list is empty

On a server you run, add three checks. Reboot the server and confirm the app and the proxy come back on their own. Try a password SSH login from another machine and confirm it is refused. The third, my own rule, runs from another network: ports 80 and 443 answer on your server’s address, and nothing else does except 22. I check 80 and 443 first, so a network that blocks everything cannot pass the test by accident.

Keep the evidence: the dated run log, the commit id that was deployed, the staging URL answering, the refused password login, and the list of fixes. A takeover of an app someone else built asks for the same kind of test.

In the Production Hardening Sprint, we verify deliverable 7.11 this way: “Deploy a fresh clone to staging through the pipeline with no step that depends on the generating tool.”

Where the sprint does this

Deliverable 7.11 is to keep the application in a version-controlled repository and on a hosting account the founder controls, deployable through a documented pipeline, independent of the tool that generated it, and we check it with the fresh-clone test in the section above. We record the result in the production readiness report, which accounts for all 123 scope IDs, keeps failures visible until resolved and explains genuine non-applicable items. Your app’s current framework and hosting setup are our starting point. Hosting, paid tools and API usage remain in your accounts, and we explain any required third-party costs before enabling them. The deliverable sits in the deployment area of the published scope.

Common questions about self-hosting and running a server

Does pm2 automatically restart?

Yes. PM2’s restart strategies page says apps “are always restarted with PM2 when crashing or exiting by default”. It brings them back after a server reboot only once pm2 startup has installed the startup script and pm2 save has stored the process list.

How do I run pm2 as a service?

Run pm2 startup without sudo: PM2 detects the init system (systemd on Ubuntu 16 and later and Debian 7 and later) and prints one command for you to copy and run, which registers PM2 to start at boot. Start your apps, then run pm2 save so the list comes back after a reboot. After a Node.js upgrade, PM2’s startup docs say to run pm2 unstartup and then pm2 startup again so the service uses the new binary.

Can you explain what a Caddyfile is and how it works?

A Caddyfile is Caddy’s plain-text config format. Its first line is the address of the site, and the directives under it, such as reverse_proxy, say how requests are handled; with one site, the braces are optional. Caddy’s docs call it “just a config adapter”: Caddy converts it into its native JSON config, which is what the server runs.

How does Caddy compare to Apache?

Both can serve a site and proxy requests to an app. Caddy obtains and renews certificates by default once a domain is in its config. Apache HTTP Server identifies each site with ServerName inside a virtual host and handles certificates through its mod_md module, which can “supervise/renew TLS certificates via the ACME protocol”. I make no speed claim either way; for one small app, the deciding factor is which config you can read and maintain.

What are the CIS Benchmarks for system hardening?

The CIS Benchmarks are the Center for Internet Security’s configuration guides, which CIS describes as “prescriptive configuration recommendations” that “represent the consensus-based effort of cybersecurity experts globally”. There are separate benchmarks per product and version, from operating systems to cloud providers and network devices, and CIS offers CIS-CAT Pro Assessor to check a system against one.

Can I deploy an app for free?

Often, for a while. Vercel, Netlify and Render each listed a free plan on their pricing pages on September 28, 2026, with limits that change, so read the host’s pricing page on the day. Some free plans rule out a business: Vercel’s docs say its Hobby plan “restricts users to non-commercial, personal use only”. My working rule: a free plan is for staging and trials, and a production app with paying customers sits on a plan whose backups and support terms you have read.