5

Can HAProxy work as web server without other web server (e.g. Apache, NGINX) behind it? I was told for such configuration but I am not sure about the accuracy of the information.

  • There are a couple of hacks you can do if you need to serve small, static files (typically < 16k), from memory (within HAProxy), like /robots.txt or /favicon.ico. Is that what you are looking for? – Michael - sqlbot Oct 10 '17 at 19:11
6

No, HAProxy is not a web server and cannot act like one. From the HAProxy Starter Guide, What HAProxy is and is not:

HAProxy is not a web server:

During startup, it isolates itself inside a chroot jail and drops its privileges, so that it will not perform any single file-system access once started. As such it cannot be turned into a web server. There are excellent open-source software for this such as Apache or Nginx, and HAProxy can be installed in front of them to provide load balancing and high availability.

6

The accepted answer of "no, it is not designed to do that" is correct.

But software being what it is, if you DO still want to do it, there is a hacky workaround.

You need an ACL pointing the request to a custom backend. For this example lets say you wanted to serve robots.txt

frontend port80
    acl is_robotstxt path /robots.txt
    use_backend robots if is_robotstxt
backend robots
    mode http
    errorfile 503 /etc/haproxy/errors/robots.http

Note that there are no servers defined for this backend, so when /robots.txt is requested and haproxy uses it, it serves a 503 error. We specify that 503 errors should serve /etc/haproxy/errors/robots.http, which luckily specifies the full HTTP output headers and all.

So in that file, we put

HTTP/1.0 200 Found
Cache-Control: no-cache
Connection: close
Content-Type: text/plain

Content here

so now what should be a 503 error is actually a 200 Found reply, content and all.

Note that doing this is not really recommended and comes with many limitations. Most obvious is that you can only serve one file this way per backend. Less obvious is that this file must fit inside haproxy's BUFSIZE, which is usually 8 or 16kB. Haproxy also is not doing any kind of sanitization on the file served this way, so its on you to serve the right headers the right way. If your clients need \r\n instead of just \n, thats on you to handle.

You're much better off just setting up a copy of your webserver of choice and just having a "static asset backend" haproxy can route to.

3

first of all - and this is important - HA-Proxy is not a webserver. This should be abundantly clear by now. However, there are ways to serve files via LUA. I came up with the following solution:

HA-Proxy configuration:

lua-load /etc/haproxy/lua/load-file.lua

...

backend lua-load-file
  http-request set-header X-LUA-LOADFILE-DOCROOT /etc/haproxy/docroot
  http-request use-service lua.load-file

As you can see I load a lua-file called load-file.lua. The contents of that file are as follows:

core.register_service("load-file", "http", function(applet)
  local docroot
  local location
  local file
  local retval
  local response
  local extension

  if(applet.path == nil or applet.headers["x-lua-loadfile-docroot"] == nil or applet.headers["x-lua-loadfile-docroot"][0] == "") then
    retval = 500
    response = "Internal Server Error"
  else
    docroot = applet.headers["x-lua-loadfile-docroot"][0]
    location = applet.path
    if(location == "" or location == "/") then
      location = "/index.html"
    end
    file = io.open(docroot .. location, "r")
    if(file == nil) then
      retval = 404
      response = "File Not Found"
    else
      retval = 200
      response = file:read("*all")
      file:close()
    end
  end

  extension = string.match(location, ".(%w+)$")
  if       extension == "css"  then applet:add_header("content-type", "text/css")
    elseif extension == "gif"  then applet:add_header("content-type", "image/gif")
    elseif extension == "htm"  then applet:add_header("content-type", "text/html")
    elseif extension == "html" then applet:add_header("content-type", "text/html")
    elseif extension == "ico"  then applet:add_header("content-type", "image/x-icon")
    elseif extension == "jpg"  then applet:add_header("content-type", "image/jpeg")
    elseif extension == "jpeg" then applet:add_header("content-type", "image/jpeg")
    elseif extension == "js"   then applet:add_header("content-type", "application/javascript; charset=UTF-8")
    elseif extension == "json" then applet:add_header("content-type", "application/json")
    elseif extension == "mpeg" then applet:add_header("content-type", "video/mpeg")
    elseif extension == "png"  then applet:add_header("content-type", "image/png")
    elseif extension == "txt"  then applet:add_header("content-type", "text/plain")
    elseif extension == "xml"  then applet:add_header("content-type", "application/xml")
    elseif extension == "zip"  then applet:add_header("content-type", "application/zip")
  end

  applet:set_status(retval)
  if(response ~= nil and response ~= "") then
    applet:add_header("content-length", string.len(response))
  end
  applet:start_response()
  applet:send(response)
end)

Basically, this code reads files from a specified document-root location in the filesystem based on the query string and generates appropriate HTTP-responses just as a normal webserver would do. That is why the script needs a document-root do be configured via the HTTP-header X-LUA-LOADFILE-DOCROOT. This header is being set via the haproxy configuration as shown in the examples. Furthermore, the code also does some very basic mimetype handling - extend the list according your needs. I hope someone finds this useful.

cheers!

  • This stops HaProxy from working. According to haproxy's LUA documentation blocking actions like io.* are only allowed in core.register_init. The trick is to split it into 2 parts: First, in initialization phase, read all files into LUA. But respect the memory limit. Second, in service phase, serve the files from memory. (Note that I'd downvote this, but do not have enough reputation yet.) – Tino Jun 16 '19 at 8:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.