diff --git a/docs/src/algorithms.md b/docs/src/algorithms.md
index 657b753..ad088f9 100644
--- a/docs/src/algorithms.md
+++ b/docs/src/algorithms.md
@@ -61,6 +61,15 @@ min_cost_assignment
 GraphsOptim.min_cost_assignment!
 ```
 
+## Minimum Vertex Cover
+
+```@docs
+min_vertex_cover
+GraphsOptim.min_vertex_cover!
+```
+
+Finds a subset $S \subset V$ of vertices of an undirected graph $G = (V,E)$ such that $\forall (u,v) \in E: u \in S \lor v \in S$
+
 ## Graph matching
 
 !!! danger "Work in progress"
diff --git a/src/GraphsOptim.jl b/src/GraphsOptim.jl
index e514036..dc03c46 100644
--- a/src/GraphsOptim.jl
+++ b/src/GraphsOptim.jl
@@ -23,6 +23,7 @@ using OptimalTransport: sinkhorn
 export min_cost_flow
 export min_cost_assignment
 export FAQ, GOAT, graph_matching
+export min_vertex_cover
 export fractional_chromatic_number, fractional_clique_number
 export shortest_path
 
@@ -30,6 +31,7 @@ include("utils.jl")
 include("flow.jl")
 include("assignment.jl")
 include("graph_matching.jl")
+include("min_vertex_cover.jl")
 include("fractional_coloring.jl")
 include("shortest_path.jl")
 
diff --git a/src/min_vertex_cover.jl b/src/min_vertex_cover.jl
new file mode 100644
index 0000000..a92a47e
--- /dev/null
+++ b/src/min_vertex_cover.jl
@@ -0,0 +1,67 @@
+"""
+    min_vertex_cover!(
+        model, g;
+        var_name
+    )
+
+Modify a JuMP model by adding the variable, constraints and objective necessary to compute a minimum vertex cove of an undirected graph.
+
+The vertex cover indicator variable will be named `var_name`
+"""
+function min_vertex_cover!(model::Model, g::AbstractGraph; integer::Bool, var_name)
+    if is_directed(g)
+        throw(ArgumentError("The graph must not be directed"))
+    end
+
+    ver = collect(vertices(g))
+
+    f = @variable(model, [ver]; integer=integer, base_name=String(var_name))
+    model[Symbol(var_name)] = f
+
+    for v in ver
+        @constraint(model, f[v] >= 0)
+        @constraint(model, f[v] <= 1)
+    end
+
+    for e in edges(g)
+        @constraint(model, f[src(e)] + f[dst(e)] >= 1)
+    end
+
+    obj = objective_function(model)
+    for v in ver
+        add_to_expression!(obj, f[v])
+    end
+    @objective(model, Min, obj)
+
+    return model
+end
+
+"""
+    min_vertex_cover(
+        g, optimizer
+    )
+
+Compute a minimum vertex cover of an undirected graph.
+
+# Arguments
+
+- `g::Graphs.AbstractGraph`: an undirected graph `G = (V, E)`
+
+# Keyword arguments
+
+- `optimizer`: JuMP-compatible solver (default is `HiGHS.Optimizer`)
+"""
+function min_vertex_cover(g::AbstractGraph; integer::Bool=true, optimizer=HiGHS.Optimizer)
+    model = Model(optimizer)
+    set_silent(model)
+
+    min_vertex_cover!(model, g; integer=integer, var_name=:cover)
+
+    optimize!(model)
+    @assert termination_status(model) == OPTIMAL
+
+    cover_values = Vector(value.(model[:cover]))
+    cover_vertices = findall(==(1), cover_values)
+
+    return cover_vertices
+end
diff --git a/test/min_vertex_cover.jl b/test/min_vertex_cover.jl
new file mode 100644
index 0000000..1a5b77e
--- /dev/null
+++ b/test/min_vertex_cover.jl
@@ -0,0 +1,18 @@
+using Graphs: SimpleGraph
+using GraphsOptim
+using Test
+
+adj_matrix = [
+    0 1 1 0 0 0
+    1 0 1 1 1 1
+    1 1 0 0 0 0
+    0 1 0 0 0 0
+    0 1 0 0 0 0
+    0 1 0 0 0 0
+]
+
+expected_ans_options = [Set([1, 2]), Set([2, 3])]
+
+graph = SimpleGraph(adj_matrix)
+
+@test Set(min_vertex_cover(graph)) in expected_ans_options
diff --git a/test/runtests.jl b/test/runtests.jl
index adfb030..8811777 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -40,6 +40,10 @@ using Test
         include("graph_matching.jl")
     end
 
+    @testset verbose = true "Vertex cover" begin
+        include("min_vertex_cover.jl")
+    end
+
     @testset verbose = true "Fractional coloring" begin
         include("fractional_coloring.jl")
     end