{"id":2173,"date":"2026-06-23T12:37:30","date_gmt":"2026-06-23T09:37:30","guid":{"rendered":"https:\/\/upcloud.com\/global\/us\/resources\/tutorials\/install-code-server-ubuntu-18-04\/"},"modified":"2026-06-23T10:40:27","modified_gmt":"2026-06-23T09:40:27","slug":"install-code-server-ubuntu","status":"publish","type":"tutorial","link":"https:\/\/upcloud.com\/global\/resources\/tutorials\/install-code-server-ubuntu\/","title":{"rendered":"How to install code-server on Ubuntu 24.04 and 26.04"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Code-server is a <a href=\"https:\/\/code.visualstudio.com\/\" target=\"_blank\" rel=\"noopener\">Visual Studio Code<\/a> instance running on a remote server, accessible through any web browser. It lets you code from any device with a browser, such as a tablet or a laptop, while keeping a single, consistent development environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Running your IDE on a cloud server also lets you offload demanding tasks such as tests, compilations, and large downloads to the remote machine. You preserve battery life when you are on the go, and you can do something else while heavy processes run on the server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This tutorial sets up code-server on a cloud server running Ubuntu, and works for both <strong>Ubuntu 24.04 LTS (Noble Numbat)<\/strong> and <strong>Ubuntu 26.04 LTS (Resolute Raccoon)<\/strong>. The steps are the same on both versions; the only difference is which template you select when deploying the server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Requirements<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Code-server needs:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A 64-bit host<\/li>\n\n\n\n<li>At least 1 GB of RAM (2 GB or more is recommended; 2 CPU cores give a smoother experience)<\/li>\n\n\n\n<li>A secure connection over HTTPS, which we will set up with Nginx and Let&#8217;s Encrypt<\/li>\n\n\n\n<li>A domain name pointing to the server, which is needed to obtain an SSL certificate<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The current releases of code-server bundle their own Node.js runtime, so you do not need to install Node.js separately.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/signup.upcloud.com\/\">Test hosting on UpCloud!<\/a><\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Sign up on UpCloud<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The first things first, you\u2019ll need to <a href=\"https:\/\/signup.upcloud.com\/\n\/\" target=\"_blank\" rel=\"noopener\">sign up for an UpCloud account<\/a>. So if you are not yet registered, create an account with UpCloud and you can get started on a free trial and continue with as little as 5 USD\/month.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once registered, log into your <a href=\"https:\/\/hub.upcloud.com\/\" target=\"_blank\" rel=\"noopener\">UpCloud Control Panel<\/a> and get cracking!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Deploy a new cloud server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Click the <a href=\"https:\/\/hub.upcloud.com\/deploy\">Deploy a server<\/a> button under the Servers section in the Control Panel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You will need to do the following:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Choose the server location from the available data centres<\/li>\n\n\n\n<li>Pick a configuration; the smallest plan is enough to get started<\/li>\n\n\n\n<li>Select the operating system: either <strong>Ubuntu 24.04 LTS (Noble Numbat)<\/strong> or <strong>Ubuntu 26.04 LTS (Resolute Raccoon)<\/strong><\/li>\n\n\n\n<li>Add an SSH key. This is required because the Ubuntu cloud-init templates use SSH key authentication and do not set a login password<\/li>\n\n\n\n<li>Give your server a hostname and description<\/li>\n\n\n\n<li>When you are happy with your selections, click Deploy<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-309-1024x1013.png\" alt=\"-\" class=\"wp-image-83444\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-310-1024x640.png\" alt=\"-\" class=\"wp-image-83446\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You can find more detailed instructions in our guide to <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/deploy-server\/\">deploying a server<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once the server has been deployed, you can find its public IP address in the server list.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Point a domain at your server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You will need a valid domain name to obtain an SSL certificate and serve code-server over HTTPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Point an A record for your chosen subdomain (for example <code>code-server.example.com<\/code>) at the public IPv4 address of your new server. This is done at your domain registrar or DNS provider. See our guide to <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/domain-name-system\/\">the domain name system and configuring DNS records<\/a> for details.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DNS changes can take a little while to propagate, so it is worth setting this up before you start, so the record has resolved by the time you request a certificate.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Connect to the server over SSH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Log in to your server over SSH as <code>root<\/code>, using the key you added during deployment. Replace <code>&lt;server-ip&gt;<\/code> with your server&#8217;s public IP address:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">ssh root@&lt;server-ip&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then update the installed packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo apt update\nsudo apt upgrade -y<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Install code-server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/github.com\/coder\/code-server\" target=\"_blank\" rel=\"noopener\">code-server project<\/a> provides an install script that detects your distribution and installs the correct package. On Ubuntu it installs the latest <code>.deb<\/code> package.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is good practice to inspect what the script will do before running it. You can do a dry run first:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">curl -fsSL https:\/\/code-server.dev\/install.sh | sh -s -- --dry-run<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When you are happy with the output, run the installer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">curl -fsSL https:\/\/code-server.dev\/install.sh | sh<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check the installed version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">code-server --version<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configure code-server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The package install already creates a configuration file at <code>~\/.config\/code-server\/config.yaml<\/code>, complete with sensible defaults and a strong, randomly generated password. Open it to take a look:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">nano ~\/.config\/code-server\/config.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It looks like this, with a different password each time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">bind-addr: 127.0.0.1:8080\nauth: password\npassword: 7f3c9a1e6b2d8045f1c4e90a\ncert: <strong>false<\/strong><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These defaults are exactly what we want, so there is nothing you need to change to continue. Binding to <code>127.0.0.1<\/code> keeps code-server reachable only from the server itself. We will put Nginx in front of it to handle the public connection and HTTPS. Setting <code>cert: false<\/code> is correct here because Nginx terminates TLS, not code-server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The generated password is secure, so you can keep it as it is. Make a note of it, since you will need it to log in later. If you would rather use a password of your own, change the <code>password:<\/code> line, then save the file. If you change it after the service is already running, restart it to pick up the change:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo systemctl restart code-server@$USER<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Start code-server as a service<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The package ships a systemd service template, so there is no need to write a service file by hand. Enable and start it for your user in one command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo systemctl enable --now code-server@$USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check that it is running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">systemctl status code-server@$USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see the service active and listening on <code>127.0.0.1:8080<\/code>. Code-server is now running in the background, but it is not yet reachable from outside the server. For that, we will set up Nginx as a reverse proxy with a Let&#8217;s Encrypt certificate.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Install and configure Nginx<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Install Nginx:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo apt install nginx -y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a configuration file for the reverse proxy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo nano \/etc\/nginx\/sites-available\/code-server.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Add the following, replacing <code>code-server.example.com<\/code> with your own domain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">server {\n    listen 80;\n    listen [::]:80;\n    server_name code-server.example.com;\n\n    location \/ {\n        proxy_pass http:\/\/localhost:8080\/;\n        proxy_set_header Host $host;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection upgrade;\n        proxy_set_header Accept-Encoding gzip;\n        proxy_http_version 1.1;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>proxy_http_version 1.1<\/code> line and the <code>Upgrade<\/code> and <code>Connection<\/code> headers are needed for the WebSocket connections that code-server relies on. Without them the editor loads but the terminal and other features fail.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the default site and enable your configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo rm \/etc\/nginx\/sites-enabled\/default\nsudo ln -s \/etc\/nginx\/sites-available\/code-server.conf \/etc\/nginx\/sites-enabled\/code-server.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Test the configuration and reload Nginx:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo nginx -t\nsudo systemctl reload nginx<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure ports 80 and 443 are open. If you use the UpCloud firewall or UFW on the server, allow both, since Let&#8217;s Encrypt validates over port 80:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo ufw allow 'Nginx Full'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Secure the connection with HTTPS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Code-server requires a secure connection, so we will obtain a free certificate from <a href=\"https:\/\/letsencrypt.org\/\" target=\"_blank\" rel=\"noopener\">Let&#8217;s Encrypt<\/a> using Certbot. Install Certbot and its Nginx plugin from the Ubuntu repositories:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo apt install certbot python3-certbot-nginx -y<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">If you would prefer the very latest Certbot client, the Certbot project recommends installing it via snap instead: <code>sudo snap install --classic certbot<\/code> followed by <code>sudo ln -s \/snap\/bin\/certbot \/usr\/bin\/certbot<\/code>. The apt package used above works the same way for our purposes, including automatic renewal.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Request and install a certificate using the Nginx plugin. Replace the domain with your own:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo certbot --nginx -d code-server.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The command runs an interactive setup. It will ask you to:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Enter a contact email, used for renewal and security notices. You can press Enter to skip this<\/li>\n\n\n\n<li>Read and agree to the Let&#8217;s Encrypt terms of service<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Certbot will then obtain the certificate, update your Nginx configuration to enable HTTPS, and add a redirect from HTTP to HTTPS. For this to work, the A record from earlier must already point at your server and have propagated.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Certbot sets up automatic renewal through a systemd timer, so the certificate will refresh before it expires without any action from you. You can confirm renewal works without making changes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">sudo certbot renew --dry-run<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Log in to code-server<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Open your domain in a web browser. You should see the code-server login page.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-311-1024x717.png\" alt=\"code-server login page\" class=\"wp-image-83454\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Log in with the password from your <code>config.yaml<\/code> (the one generated during install, unless you changed it). Once logged in, you have a full VS Code environment running on your own cloud server.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/upcloud.com\/media\/image-312-1024x643.png\" alt=\"-\" class=\"wp-image-83455\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To open a terminal inside the editor, press Ctrl and the backtick key (<code>`<\/code>, just under Escape), or open the menu (the icon in the top-left of the window) and choose Terminal, then New Terminal. This gives you a shell on the server, the same as your SSH session.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Keeping code-server up to date<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To update code-server later, run the install script again. It will detect the existing installation and upgrade it to the latest version:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">curl -fsSL https:\/\/code-server.dev\/install.sh | sh\nsudo systemctl restart code-server@$USER<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Best practices for production use<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The setup above is enough to get you coding, and is fine for a personal test server. If you intend to rely on the server for real work, there are a few things worth tightening first.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run code-server as a dedicated user rather than root.<\/strong> <br>Anyone who logs in to code-server gets a terminal with the privileges of the user the service runs as. In this guide, that user is root, so a leaked password means a root shell on the server. For production, create a dedicated user with only the access it needs, place the config file under that user&#8217;s home directory, and enable the service for them with <code>sudo systemctl enable --now code-server@&lt;username&gt;<\/code>. The service template handles the rest.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Keep the password strong, and treat it as the only lock on the door.<\/strong> <br>Code-server has no rate limiting or second factor by default, so the password is the entire gate between the internet and a shell on your server. Keep the generated one or set a long, unique password of your own, and do not reuse it elsewhere.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Limit who can reach the server at the network layer.<\/strong> <br>Because the only authentication is a password, reducing exposure is a meaningful second line of defence. You can use the UpCloud firewall to allow ports 80 and 443 only from IP addresses you trust, or place the server behind a VPN so it is not open to the whole internet. See our guide to the <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/configure-firewall\/\">UpCloud firewall<\/a> for how to set this up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Enable backups.<\/strong> <br>If you are keeping work on the server, turn on <a href=\"https:\/\/upcloud.com\/global\/docs\/guides\/taking-backups\/\">UpCloud&#8217;s Simple Backups or Flexible Backups<\/a> so you have a recent copy to restore from. Backups are configured per server in the Control Panel.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Keep everything patched.<\/strong> <br>Update code-server with the install script as shown above, run <code>sudo apt update &amp;&amp; sudo apt upgrade<\/code> regularly for the operating system, and let Certbot&#8217;s timer handle certificate renewal on its own.<\/p>\n","protected":false},"author":23,"featured_media":14657,"comment_status":"open","ping_status":"closed","template":"","community-category":[223],"class_list":["post-2173","tutorial","type-tutorial","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/2173","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial"}],"about":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/types\/tutorial"}],"author":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/users\/23"}],"replies":[{"embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/comments?post=2173"}],"version-history":[{"count":1,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/2173\/revisions"}],"predecessor-version":[{"id":7544,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/tutorial\/2173\/revisions\/7544"}],"wp:attachment":[{"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/media?parent=2173"}],"wp:term":[{"taxonomy":"community-category","embeddable":true,"href":"https:\/\/upcloud.com\/global\/wp-json\/wp\/v2\/community-category?post=2173"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}